Add single file debugging support to dbgshim (#2941)
authorMike McLaughlin <mikem@microsoft.com>
Mon, 28 Mar 2022 20:40:48 +0000 (13:40 -0700)
committerGitHub <noreply@github.com>
Mon, 28 Mar 2022 20:40:48 +0000 (13:40 -0700)
Add single file debugging support to dbgshim

Add CreateDebuggingInterfaceFromVersion3/RegisterRuntimeStartup3 APIs that supports a ICLRDebuggingLibraryProvider3 to find the DBI/DAC for a single-file app.

Add ICLRDebuggingLibraryProvider3 for Windows/Unix support.

Add machoreader from runtime. Change both elf and macho readers to handle file layout. Add functions to get build id.

Add dbgshim unit tests.

Add InstallRuntimes hoisting logic.

Fix OpenVirtualProcess on Windows single-file dump

Fix and add test timeouts

* More logging

* More logging

* Change debugger version; less logging

* Add dump on hang to the remote invoke helper

* Fixes to dump logic

* RemoteExecutorHelper.RemoteInvoke fixes

* Fix the rid on Alpine for single-file apps

* The linux dump assets used by OpenVirtualProcess are not supported on alpine/musl

* Undo enabling SOS tests on Alpine.

* Restore the timeouts back to 5 minutes

* Make DebugServices unit tests more reliable

* Skip MacOS single-file tests

* Remove some timeouts since RemoteInvoke has one

* Disable checking VersionData property in the DebugServices.UnitTests

* Fix bug with spaces in module names in createdump on Linux

* Code review feedback

* Code review feedback

84 files changed:
diagnostics.sln
eng/InstallRuntimes.proj
eng/Versions.props
src/Directory.Build.targets
src/Microsoft.Diagnostics.DebugServices.Implementation/Module.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs
src/Microsoft.Diagnostics.ExtensionCommands/ClrModulesCommand.cs
src/Microsoft.Diagnostics.ExtensionCommands/Host/ModulesCommand.cs
src/Microsoft.Diagnostics.ExtensionCommands/Host/RuntimesCommand.cs
src/Microsoft.Diagnostics.TestHelpers/CharToLineConverter.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.TestHelpers/ConsoleTestOutputHelper.cs
src/Microsoft.Diagnostics.TestHelpers/DotNetBuildDebuggeeTestStep.cs
src/Microsoft.Diagnostics.TestHelpers/LoggingListener.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.TestHelpers/Microsoft.Diagnostics.TestHelpers.csproj
src/Microsoft.Diagnostics.TestHelpers/ProcessRunner.cs
src/Microsoft.Diagnostics.TestHelpers/RemoteExecutorHelper.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.TestHelpers/TestConfiguration.cs
src/Microsoft.Diagnostics.TestHelpers/TestHost/TestDataReader.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.TestHelpers/TestHost/TestDataWriter.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.TestHelpers/TestHost/TestDump.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.TestHelpers/TestHost/TestHost.cs [new file with mode: 0644]
src/SOS/SOS.Extensions/DebuggerServices.cs
src/SOS/SOS.Extensions/RemoteMemoryService.cs
src/SOS/SOS.Hosting/CorDebugDataTargetWrapper.cs
src/SOS/SOS.Hosting/RuntimeWrapper.cs
src/SOS/SOS.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt
src/SOS/SOS.UnitTests/ConfigFiles/Windows/Debugger.Tests.Config.txt
src/SOS/SOS.UnitTests/SOS.UnitTests.csproj
src/SOS/Strike/Strike.vcxproj
src/SOS/Strike/platform/runtimeimpl.cpp
src/SOS/Strike/util.cpp
src/SOS/Strike/util.h
src/SOS/extensions/extensions.vcxproj
src/SOS/lldbplugin/lldbplugin.vcxproj
src/SOS/lldbplugin/services.cpp
src/dbgshim/CMakeLists.txt
src/dbgshim/dbgshim.cpp
src/dbgshim/dbgshim.h
src/dbgshim/dbgshim.ntdef
src/dbgshim/dbgshim.vcxproj
src/dbgshim/dbgshim.vcxproj.filters
src/dbgshim/dbgshim_unixexports.src
src/dbgshim/debugshim.cpp
src/dbgshim/debugshim.h
src/shared/dbgutil/CMakeLists.txt
src/shared/dbgutil/dbgutil.vcxproj
src/shared/dbgutil/elfreader.cpp
src/shared/dbgutil/elfreader.h
src/shared/dbgutil/machoreader.cpp [new file with mode: 0644]
src/shared/dbgutil/machoreader.h [new file with mode: 0644]
src/shared/gcdump/gcdump.vcxproj
src/shared/inc/CMakeLists.txt
src/shared/inc/llvm/ELF.h
src/shared/inc/metahost.idl
src/shared/inc/runtimeinfo.h
src/shared/pal/inc/pal.h
src/shared/pal/prebuilt/idl/metahost_i.cpp [new file with mode: 0644]
src/shared/pal/prebuilt/inc/metahost.h
src/shared/pal/src/include/pal/procobj.hpp
src/shared/pal/src/thread/process.cpp
src/shared/utilcode/utilcode.vcxproj
src/tests/DbgShim.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt [new file with mode: 0644]
src/tests/DbgShim.UnitTests/ConfigFiles/Windows/Debugger.Tests.Config.txt [new file with mode: 0644]
src/tests/DbgShim.UnitTests/DbgShim.UnitTests.csproj [new file with mode: 0644]
src/tests/DbgShim.UnitTests/DbgShimAPI.cs [new file with mode: 0644]
src/tests/DbgShim.UnitTests/DbgShimTests.cs [new file with mode: 0644]
src/tests/DbgShim.UnitTests/DebuggeeInfo.cs [new file with mode: 0644]
src/tests/DbgShim.UnitTests/Debuggees/SimpleDebuggee/SimpleDebuggee.cs [new file with mode: 0644]
src/tests/DbgShim.UnitTests/Debuggees/SimpleDebuggee/SimpleDebuggee.csproj [new file with mode: 0644]
src/tests/DbgShim.UnitTests/Debuggees/SimpleDebuggee/runtimeconfig.template.json [new file with mode: 0644]
src/tests/DbgShim.UnitTests/ICLRDebugging.cs [new file with mode: 0644]
src/tests/DbgShim.UnitTests/ICorDebug.cs [new file with mode: 0644]
src/tests/DbgShim.UnitTests/ICorDebugController.cs [new file with mode: 0644]
src/tests/DbgShim.UnitTests/LibraryProviderWrapper.cs [new file with mode: 0644]
src/tests/DbgShim.UnitTests/ManagedCallbackWrapper.cs [new file with mode: 0644]
src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt
src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/DebugServicesTests.cs
src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/Microsoft.Diagnostics.DebugServices.UnitTests.csproj
src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestDataReader.cs [deleted file]
src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestDataWriter.cs [deleted file]
src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestDbgEng.cs
src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestDump.cs [deleted file]
src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestHost.cs [deleted file]
src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/WriteTestData.cs

index 3765a7faf524119f34dae1b5a5aec5d6037f221b..5f940760e0bf7deda298d940c51d0a06fd7d9d89 100644 (file)
@@ -41,6 +41,9 @@ EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SOS.Symbol.Package", "src\SOS\SOS.Package\SOS.Symbol.Package.csproj", "{410394E0-7F4F-42D5-B5FA-30956F44ACBC}"
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{03479E19-3F18-49A6-910A-F5041E27E7C0}"
+       ProjectSection(SolutionItems) = preProject
+               src\tests\eventpipe\EventPipe.UnitTests.csproj = src\tests\eventpipe\EventPipe.UnitTests.csproj
+       EndProjectSection
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotnetTrace.UnitTests", "src\tests\dotnet-trace\DotnetTrace.UnitTests.csproj", "{AEDCCF5B-5AD0-4D64-BF73-5CF468E07D22}"
 EndProject
@@ -70,137 +73,137 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.Monit
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "inc", "inc", "{41BDFD6D-D165-4D67-BEF6-4E539040D30A}"
        ProjectSection(SolutionItems) = preProject
-               src\inc\arrayholder.h = src\inc\arrayholder.h
-               src\inc\bitvector.h = src\inc\bitvector.h
-               src\inc\check.h = src\inc\check.h
-               src\inc\check.inl = src\inc\check.inl
-               src\inc\clrdata.idl = src\inc\clrdata.idl
-               src\inc\clrhost.h = src\inc\clrhost.h
-               src\inc\clrinternal.idl = src\inc\clrinternal.idl
-               src\inc\clrnt.h = src\inc\clrnt.h
-               src\inc\clrprivappxhosting.idl = src\inc\clrprivappxhosting.idl
-               src\inc\clrprivbinding.idl = src\inc\clrprivbinding.idl
-               src\inc\clrprivhosting.idl = src\inc\clrprivhosting.idl
-               src\inc\clrprivruntimebinders.idl = src\inc\clrprivruntimebinders.idl
-               src\inc\clrtypes.h = src\inc\clrtypes.h
-               src\inc\CMakeLists.txt = src\inc\CMakeLists.txt
-               src\inc\contract.h = src\inc\contract.h
-               src\inc\contract.inl = src\inc\contract.inl
-               src\inc\cor.h = src\inc\cor.h
-               src\inc\cordebug.idl = src\inc\cordebug.idl
-               src\inc\coreclrhost.h = src\inc\coreclrhost.h
-               src\inc\corexcep.h = src\inc\corexcep.h
-               src\inc\corhdr.h = src\inc\corhdr.h
-               src\inc\corhlpr.cpp = src\inc\corhlpr.cpp
-               src\inc\corhlpr.h = src\inc\corhlpr.h
-               src\inc\corhlprpriv.h = src\inc\corhlprpriv.h
-               src\inc\corjitflags.h = src\inc\corjitflags.h
-               src\inc\corprof.idl = src\inc\corprof.idl
-               src\inc\corpub.idl = src\inc\corpub.idl
-               src\inc\corsym.idl = src\inc\corsym.idl
-               src\inc\cortypeinfo.h = src\inc\cortypeinfo.h
-               src\inc\crosscomp.h = src\inc\crosscomp.h
-               src\inc\crsttypes.h = src\inc\crsttypes.h
-               src\inc\crtwrap.h = src\inc\crtwrap.h
-               src\inc\daccess.h = src\inc\daccess.h
-               src\inc\dacprivate.h = src\inc\dacprivate.h
-               src\inc\dbgenginemetrics.h = src\inc\dbgenginemetrics.h
-               src\inc\dbgportable.h = src\inc\dbgportable.h
-               src\inc\dbgtargetcontext.h = src\inc\dbgtargetcontext.h
-               src\inc\dbgutil.h = src\inc\dbgutil.h
-               src\inc\debugmacros.h = src\inc\debugmacros.h
-               src\inc\debugmacrosext.h = src\inc\debugmacrosext.h
-               src\inc\debugreturn.h = src\inc\debugreturn.h
-               src\inc\entrypoints.h = src\inc\entrypoints.h
-               src\inc\ex.h = src\inc\ex.h
-               src\inc\fstring.h = src\inc\fstring.h
-               src\inc\fusion.idl = src\inc\fusion.idl
-               src\inc\gcdecoder.cpp = src\inc\gcdecoder.cpp
-               src\inc\gcdesc.h = src\inc\gcdesc.h
-               src\inc\gcdump.h = src\inc\gcdump.h
-               src\inc\gcinfo.h = src\inc\gcinfo.h
-               src\inc\gcinfodecoder.h = src\inc\gcinfodecoder.h
-               src\inc\gcinfodumper.h = src\inc\gcinfodumper.h
-               src\inc\gcinfotypes.h = src\inc\gcinfotypes.h
-               src\inc\gcmsg.inl = src\inc\gcmsg.inl
-               src\inc\genericstackprobe.h = src\inc\genericstackprobe.h
-               src\inc\getproductversionnumber.h = src\inc\getproductversionnumber.h
-               src\inc\hillclimbing.h = src\inc\hillclimbing.h
-               src\inc\holder.h = src\inc\holder.h
-               src\inc\iallocator.h = src\inc\iallocator.h
-               src\inc\iterator.h = src\inc\iterator.h
-               src\inc\livedatatarget.h = src\inc\livedatatarget.h
-               src\inc\log.h = src\inc\log.h
-               src\inc\loglf.h = src\inc\loglf.h
-               src\inc\longfilepathwrappers.h = src\inc\longfilepathwrappers.h
-               src\inc\mdcommon.h = src\inc\mdcommon.h
-               src\inc\memoryrange.h = src\inc\memoryrange.h
-               src\inc\metadata.h = src\inc\metadata.h
-               src\inc\metahost.idl = src\inc\metahost.idl
-               src\inc\MSCOREE.IDL = src\inc\MSCOREE.IDL
-               src\inc\mscorsvc.idl = src\inc\mscorsvc.idl
-               src\inc\msodw.h = src\inc\msodw.h
-               src\inc\new.hpp = src\inc\new.hpp
-               src\inc\nibblemapmacros.h = src\inc\nibblemapmacros.h
-               src\inc\nsutilpriv.h = src\inc\nsutilpriv.h
-               src\inc\opcode.def = src\inc\opcode.def
-               src\inc\openum.h = src\inc\openum.h
-               src\inc\ostype.h = src\inc\ostype.h
-               src\inc\palclr.h = src\inc\palclr.h
-               src\inc\palclr_win.h = src\inc\palclr_win.h
-               src\inc\pedecoder.h = src\inc\pedecoder.h
-               src\inc\pedecoder.inl = src\inc\pedecoder.inl
-               src\inc\predeftlsslot.h = src\inc\predeftlsslot.h
-               src\inc\random.h = src\inc\random.h
-               src\inc\readytorun.h = src\inc\readytorun.h
-               src\inc\readytoruninstructionset.h = src\inc\readytoruninstructionset.h
-               src\inc\regdisp.h = src\inc\regdisp.h
-               src\inc\registrywrapper.h = src\inc\registrywrapper.h
-               src\inc\releaseholder.h = src\inc\releaseholder.h
-               src\inc\resource.h = src\inc\resource.h
-               src\inc\runtimeinfo.h = src\inc\runtimeinfo.h
-               src\inc\safemath.h = src\inc\safemath.h
-               src\inc\safewrap.h = src\inc\safewrap.h
-               src\inc\sbuffer.h = src\inc\sbuffer.h
-               src\inc\sbuffer.inl = src\inc\sbuffer.inl
-               src\inc\securityutil.h = src\inc\securityutil.h
-               src\inc\securitywrapper.h = src\inc\securitywrapper.h
-               src\inc\sigparser.h = src\inc\sigparser.h
-               src\inc\sospriv.idl = src\inc\sospriv.idl
-               src\inc\sstring.h = src\inc\sstring.h
-               src\inc\sstring.inl = src\inc\sstring.inl
-               src\inc\stacktrace.h = src\inc\stacktrace.h
-               src\inc\static_assert.h = src\inc\static_assert.h
-               src\inc\staticcontract.h = src\inc\staticcontract.h
-               src\inc\stdmacros.h = src\inc\stdmacros.h
-               src\inc\stresslog.h = src\inc\stresslog.h
-               src\inc\switches.h = src\inc\switches.h
-               src\inc\tls.h = src\inc\tls.h
-               src\inc\unreachable.h = src\inc\unreachable.h
-               src\inc\utilcode.h = src\inc\utilcode.h
-               src\inc\volatile.h = src\inc\volatile.h
-               src\inc\warningcontrol.h = src\inc\warningcontrol.h
-               src\inc\win64unwind.h = src\inc\win64unwind.h
-               src\inc\winwrap.h = src\inc\winwrap.h
-               src\inc\xclrdata.idl = src\inc\xclrdata.idl
-               src\inc\xcordebug.idl = src\inc\xcordebug.idl
-               src\inc\yieldprocessornormalized.h = src\inc\yieldprocessornormalized.h
+               src\shared\inc\arrayholder.h = src\shared\inc\arrayholder.h
+               src\shared\inc\bitvector.h = src\shared\inc\bitvector.h
+               src\shared\inc\check.h = src\shared\inc\check.h
+               src\shared\inc\check.inl = src\shared\inc\check.inl
+               src\shared\inc\clrdata.idl = src\shared\inc\clrdata.idl
+               src\shared\inc\clrhost.h = src\shared\inc\clrhost.h
+               src\shared\inc\clrinternal.idl = src\shared\inc\clrinternal.idl
+               src\shared\inc\clrnt.h = src\shared\inc\clrnt.h
+               src\shared\inc\clrprivappxhosting.idl = src\shared\inc\clrprivappxhosting.idl
+               src\shared\inc\clrprivbinding.idl = src\shared\inc\clrprivbinding.idl
+               src\shared\inc\clrprivhosting.idl = src\shared\inc\clrprivhosting.idl
+               src\shared\inc\clrprivruntimebinders.idl = src\shared\inc\clrprivruntimebinders.idl
+               src\shared\inc\clrtypes.h = src\shared\inc\clrtypes.h
+               src\shared\inc\CMakeLists.txt = src\shared\inc\CMakeLists.txt
+               src\shared\inc\contract.h = src\shared\inc\contract.h
+               src\shared\inc\contract.inl = src\shared\inc\contract.inl
+               src\shared\inc\cor.h = src\shared\inc\cor.h
+               src\shared\inc\cordebug.idl = src\shared\inc\cordebug.idl
+               src\shared\inc\coreclrhost.h = src\shared\inc\coreclrhost.h
+               src\shared\inc\corexcep.h = src\shared\inc\corexcep.h
+               src\shared\inc\corhdr.h = src\shared\inc\corhdr.h
+               src\shared\inc\corhlpr.cpp = src\shared\inc\corhlpr.cpp
+               src\shared\inc\corhlpr.h = src\shared\inc\corhlpr.h
+               src\shared\inc\corhlprpriv.h = src\shared\inc\corhlprpriv.h
+               src\shared\inc\corjitflags.h = src\shared\inc\corjitflags.h
+               src\shared\inc\corprof.idl = src\shared\inc\corprof.idl
+               src\shared\inc\corpub.idl = src\shared\inc\corpub.idl
+               src\shared\inc\corsym.idl = src\shared\inc\corsym.idl
+               src\shared\inc\cortypeinfo.h = src\shared\inc\cortypeinfo.h
+               src\shared\inc\crosscomp.h = src\shared\inc\crosscomp.h
+               src\shared\inc\crsttypes.h = src\shared\inc\crsttypes.h
+               src\shared\inc\crtwrap.h = src\shared\inc\crtwrap.h
+               src\shared\inc\daccess.h = src\shared\inc\daccess.h
+               src\shared\inc\dacprivate.h = src\shared\inc\dacprivate.h
+               src\shared\inc\dbgenginemetrics.h = src\shared\inc\dbgenginemetrics.h
+               src\shared\inc\dbgportable.h = src\shared\inc\dbgportable.h
+               src\shared\inc\dbgtargetcontext.h = src\shared\inc\dbgtargetcontext.h
+               src\shared\inc\dbgutil.h = src\shared\inc\dbgutil.h
+               src\shared\inc\debugmacros.h = src\shared\inc\debugmacros.h
+               src\shared\inc\debugmacrosext.h = src\shared\inc\debugmacrosext.h
+               src\shared\inc\debugreturn.h = src\shared\inc\debugreturn.h
+               src\shared\inc\entrypoints.h = src\shared\inc\entrypoints.h
+               src\shared\inc\ex.h = src\shared\inc\ex.h
+               src\shared\inc\fstring.h = src\shared\inc\fstring.h
+               src\shared\inc\fusion.idl = src\shared\inc\fusion.idl
+               src\shared\inc\gcdecoder.cpp = src\shared\inc\gcdecoder.cpp
+               src\shared\inc\gcdesc.h = src\shared\inc\gcdesc.h
+               src\shared\inc\gcdump.h = src\shared\inc\gcdump.h
+               src\shared\inc\gcinfo.h = src\shared\inc\gcinfo.h
+               src\shared\inc\gcinfodecoder.h = src\shared\inc\gcinfodecoder.h
+               src\shared\inc\gcinfodumper.h = src\shared\inc\gcinfodumper.h
+               src\shared\inc\gcinfotypes.h = src\shared\inc\gcinfotypes.h
+               src\shared\inc\gcmsg.inl = src\shared\inc\gcmsg.inl
+               src\shared\inc\genericstackprobe.h = src\shared\inc\genericstackprobe.h
+               src\shared\inc\getproductversionnumber.h = src\shared\inc\getproductversionnumber.h
+               src\shared\inc\hillclimbing.h = src\shared\inc\hillclimbing.h
+               src\shared\inc\holder.h = src\shared\inc\holder.h
+               src\shared\inc\iallocator.h = src\shared\inc\iallocator.h
+               src\shared\inc\iterator.h = src\shared\inc\iterator.h
+               src\shared\inc\livedatatarget.h = src\shared\inc\livedatatarget.h
+               src\shared\inc\log.h = src\shared\inc\log.h
+               src\shared\inc\loglf.h = src\shared\inc\loglf.h
+               src\shared\inc\longfilepathwrappers.h = src\shared\inc\longfilepathwrappers.h
+               src\shared\inc\mdcommon.h = src\shared\inc\mdcommon.h
+               src\shared\inc\memoryrange.h = src\shared\inc\memoryrange.h
+               src\shared\inc\metadata.h = src\shared\inc\metadata.h
+               src\shared\inc\metahost.idl = src\shared\inc\metahost.idl
+               src\shared\inc\MSCOREE.IDL = src\shared\inc\MSCOREE.IDL
+               src\shared\inc\mscorsvc.idl = src\shared\inc\mscorsvc.idl
+               src\shared\inc\msodw.h = src\shared\inc\msodw.h
+               src\shared\inc\new.hpp = src\shared\inc\new.hpp
+               src\shared\inc\nibblemapmacros.h = src\shared\inc\nibblemapmacros.h
+               src\shared\inc\nsutilpriv.h = src\shared\inc\nsutilpriv.h
+               src\shared\inc\opcode.def = src\shared\inc\opcode.def
+               src\shared\inc\openum.h = src\shared\inc\openum.h
+               src\shared\inc\ostype.h = src\shared\inc\ostype.h
+               src\shared\inc\palclr.h = src\shared\inc\palclr.h
+               src\shared\inc\palclr_win.h = src\shared\inc\palclr_win.h
+               src\shared\inc\pedecoder.h = src\shared\inc\pedecoder.h
+               src\shared\inc\pedecoder.inl = src\shared\inc\pedecoder.inl
+               src\shared\inc\predeftlsslot.h = src\shared\inc\predeftlsslot.h
+               src\shared\inc\random.h = src\shared\inc\random.h
+               src\shared\inc\readytorun.h = src\shared\inc\readytorun.h
+               src\shared\inc\readytoruninstructionset.h = src\shared\inc\readytoruninstructionset.h
+               src\shared\inc\regdisp.h = src\shared\inc\regdisp.h
+               src\shared\inc\registrywrapper.h = src\shared\inc\registrywrapper.h
+               src\shared\inc\releaseholder.h = src\shared\inc\releaseholder.h
+               src\shared\inc\resource.h = src\shared\inc\resource.h
+               src\shared\inc\runtimeinfo.h = src\shared\inc\runtimeinfo.h
+               src\shared\inc\safemath.h = src\shared\inc\safemath.h
+               src\shared\inc\safewrap.h = src\shared\inc\safewrap.h
+               src\shared\inc\sbuffer.h = src\shared\inc\sbuffer.h
+               src\shared\inc\sbuffer.inl = src\shared\inc\sbuffer.inl
+               src\shared\inc\securityutil.h = src\shared\inc\securityutil.h
+               src\shared\inc\securitywrapper.h = src\shared\inc\securitywrapper.h
+               src\shared\inc\sigparser.h = src\shared\inc\sigparser.h
+               src\shared\inc\sospriv.idl = src\shared\inc\sospriv.idl
+               src\shared\inc\sstring.h = src\shared\inc\sstring.h
+               src\shared\inc\sstring.inl = src\shared\inc\sstring.inl
+               src\shared\inc\stacktrace.h = src\shared\inc\stacktrace.h
+               src\shared\inc\static_assert.h = src\shared\inc\static_assert.h
+               src\shared\inc\staticcontract.h = src\shared\inc\staticcontract.h
+               src\shared\inc\stdmacros.h = src\shared\inc\stdmacros.h
+               src\shared\inc\stresslog.h = src\shared\inc\stresslog.h
+               src\shared\inc\switches.h = src\shared\inc\switches.h
+               src\shared\inc\tls.h = src\shared\inc\tls.h
+               src\shared\inc\unreachable.h = src\shared\inc\unreachable.h
+               src\shared\inc\utilcode.h = src\shared\inc\utilcode.h
+               src\shared\inc\volatile.h = src\shared\inc\volatile.h
+               src\shared\inc\warningcontrol.h = src\shared\inc\warningcontrol.h
+               src\shared\inc\win64unwind.h = src\shared\inc\win64unwind.h
+               src\shared\inc\winwrap.h = src\shared\inc\winwrap.h
+               src\shared\inc\xclrdata.idl = src\shared\inc\xclrdata.idl
+               src\shared\inc\xcordebug.idl = src\shared\inc\xcordebug.idl
+               src\shared\inc\yieldprocessornormalized.h = src\shared\inc\yieldprocessornormalized.h
        EndProjectSection
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "clr_std", "clr_std", "{33239640-6F4B-4DA4-A780-2F5B26D57EAD}"
        ProjectSection(SolutionItems) = preProject
-               src\inc\clr_std\algorithm = src\inc\clr_std\algorithm
-               src\inc\clr_std\string = src\inc\clr_std\string
-               src\inc\clr_std\type_traits = src\inc\clr_std\type_traits
-               src\inc\clr_std\utility = src\inc\clr_std\utility
-               src\inc\clr_std\vector = src\inc\clr_std\vector
+               src\shared\inc\clr_std\algorithm = src\shared\inc\clr_std\algorithm
+               src\shared\inc\clr_std\string = src\shared\inc\clr_std\string
+               src\shared\inc\clr_std\type_traits = src\shared\inc\clr_std\type_traits
+               src\shared\inc\clr_std\utility = src\shared\inc\clr_std\utility
+               src\shared\inc\clr_std\vector = src\shared\inc\clr_std\vector
        EndProjectSection
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "llvm", "llvm", "{06730767-421B-465F-BB63-A3A07D72D7A2}"
        ProjectSection(SolutionItems) = preProject
-               src\inc\llvm\Dwarf.def = src\inc\llvm\Dwarf.def
-               src\inc\llvm\Dwarf.h = src\inc\llvm\Dwarf.h
-               src\inc\llvm\ELF.h = src\inc\llvm\ELF.h
+               src\shared\inc\llvm\Dwarf.def = src\shared\inc\llvm\Dwarf.def
+               src\shared\inc\llvm\Dwarf.h = src\shared\inc\llvm\Dwarf.h
+               src\shared\inc\llvm\ELF.h = src\shared\inc\llvm\ELF.h
        EndProjectSection
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.Monitoring.UnitTests", "src\tests\Microsoft.Diagnostics.Monitoring\Microsoft.Diagnostics.Monitoring.UnitTests.csproj", "{6419BA04-6F1A-4D2F-8DE4-5C359E0364A3}"
@@ -260,6 +263,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "minipal", "minipal", "{795B
                src\shared\minipal\utils.h = src\shared\minipal\utils.h
        EndProjectSection
 EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DbgShim.UnitTests", "src\tests\DbgShim.UnitTests\DbgShim.UnitTests.csproj", "{DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}"
+EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
                Checked|Any CPU = Checked|Any CPU
@@ -1741,6 +1746,46 @@ Global
                {8C35FEF8-1101-38F6-ACD0-462A1EA53A7D}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
                {8C35FEF8-1101-38F6-ACD0-462A1EA53A7D}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
                {8C35FEF8-1101-38F6-ACD0-462A1EA53A7D}.RelWithDebInfo|x86.ActiveCfg = RelWithDebInfo|x64
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Checked|Any CPU.ActiveCfg = Debug|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Checked|Any CPU.Build.0 = Debug|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Checked|ARM.ActiveCfg = Debug|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Checked|ARM.Build.0 = Debug|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Checked|ARM64.ActiveCfg = Debug|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Checked|ARM64.Build.0 = Debug|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Checked|x64.ActiveCfg = Debug|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Checked|x64.Build.0 = Debug|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Checked|x86.ActiveCfg = Debug|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Checked|x86.Build.0 = Debug|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Debug|ARM.ActiveCfg = Debug|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Debug|ARM.Build.0 = Debug|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Debug|ARM64.Build.0 = Debug|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Debug|x64.ActiveCfg = Debug|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Debug|x64.Build.0 = Debug|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Debug|x86.ActiveCfg = Debug|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Debug|x86.Build.0 = Debug|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Release|Any CPU.Build.0 = Release|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Release|ARM.ActiveCfg = Release|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Release|ARM.Build.0 = Release|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Release|ARM64.ActiveCfg = Release|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Release|ARM64.Build.0 = Release|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Release|x64.ActiveCfg = Release|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Release|x64.Build.0 = Release|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Release|x86.ActiveCfg = Release|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.Release|x86.Build.0 = Release|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.RelWithDebInfo|ARM64.ActiveCfg = Release|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.RelWithDebInfo|ARM64.Build.0 = Release|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
        EndGlobalSection
        GlobalSection(SolutionProperties) = preSolution
                HideSolutionNode = FALSE
@@ -1796,6 +1841,7 @@ Global
                {20EBC3C4-917C-402D-B778-9A6E3742BF5A} = {7852EDE4-93DF-4DB1-8A86-C521703811AF}
                {8C35FEF8-1101-38F6-ACD0-462A1EA53A7D} = {7852EDE4-93DF-4DB1-8A86-C521703811AF}
                {795B7A50-1B1F-406E-94E0-F9B35104EF22} = {7852EDE4-93DF-4DB1-8A86-C521703811AF}
+               {DD60B7C4-BECC-4C7D-A53B-FCDD4C92B728} = {03479E19-3F18-49A6-910A-F5041E27E7C0}
        EndGlobalSection
        GlobalSection(ExtensibilityGlobals) = postSolution
                SolutionGuid = {46465737-C938-44FC-BE1A-4CE139EBB5E0}
index 1cc189bb5656223cc40bc029005abf4a4e24cddf..c11cd5f6aceb77942a8187e310b17ada94d30cdf 100644 (file)
@@ -1,5 +1,5 @@
 <!-- All Rights Reserved. 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. -->
-<Project>
+<Project Sdk="Microsoft.Build.NoTargets">
   <!--
      $(BuildArch) - architecture to test (x64, x86, arm, arm64). Defaults to x64.
      $(PrivateBuildPath) - if non-empty, path to private runtime build to copy/test
@@ -24,6 +24,8 @@
      $(MicrosoftNETCoreApp50Version) $(MicrosoftAspNetCoreApp50Version)  - 5.0 version
      $(MicrosoftNETCoreApp31Version) $(MicrosoftAspNetCoreApp31Version)  - 3.1 version
 
+     $(SingleFileRuntimeVersion) - The version of the runtime used to build single-file apps
+
      From Arcade:
 
      $(RepoRoot) - the root of the diagnostics repo
 
   <PropertyGroup>
     <BuildArch Condition="'$(BuildArch)' == ''">$(Platform)</BuildArch>
-    <BuildArch Condition="'$(BuildArch)' == ''">x64</BuildArch>
+    <BuildArch Condition="'$(BuildArch)' == 'AnyCpu'">$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture.ToString().ToLowerInvariant)</BuildArch>
     <PrivateBuildTesting>false</PrivateBuildTesting>
     <PrivateBuildTesting Condition="'$(PrivateBuildPath)' != ''">true</PrivateBuildTesting>
+    <DotnetRuntimeVersion Condition="'$(DotnetRuntimeVersion)' == ''">default</DotnetRuntimeVersion>
     <InternalReleaseTesting>false</InternalReleaseTesting>
     <InternalReleaseTesting Condition="'$(DotnetRuntimeVersion)' != 'default'">true</InternalReleaseTesting>
     <ExtraInstallArgs>-runtimesourcefeed '$(RuntimeSourceFeed)' -runtimesourcefeedkey '$(RuntimeSourceFeedKey)'</ExtraInstallArgs>
@@ -96,7 +99,6 @@
 -->
 
   <Target Name="InstallTestRuntimes" 
-          BeforeTargets="RunTests"
           DependsOnTargets="CleanupVersionManifest;InstallRuntimesWindows;InstallRuntimesUnix;CopyPrivateBuild;WriteTestVersionManifest;" />
 
 <!--
 
   <RuntimeVersionLatest>$(RuntimeVersionLatest)</RuntimeVersionLatest>
   <AspNetCoreVersionLatest>$(AspNetCoreVersionLatest)</AspNetCoreVersionLatest>
+
+  <SingleFileRuntimeVersion>$(SingleFileRuntimeVersion)</SingleFileRuntimeVersion>
 </Configuration>
 ]]>
       </TestConfigFileLines>
index 1d030fb585e48b23edbd12607b0ef1fa8e85434b..c001ae1967f351aa03a9a6317e3260da58f67940 100644 (file)
@@ -15,6 +15,8 @@
     <SkipPackagePublishingVersionChecks>true</SkipPackagePublishingVersionChecks>
   </PropertyGroup>
   <PropertyGroup>
+    <!-- The SDK runtime version used to build single-file apps (currently hardcoded) -->
+    <SingleFileRuntimeVersion>6.0.3</SingleFileRuntimeVersion>
     <!-- Latest symstore version updated by darc -->
     <MicrosoftSymbolStoreVersion>1.0.317101</MicrosoftSymbolStoreVersion>
     <!-- Runtime versions to test -->
index 9759f0bb728023e8f9edc0c9bf8259fe88391628..6f6f369a608fe2af91f2aa1ff79c2879fe323ca6 100644 (file)
@@ -1,21 +1,27 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project>
-  <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />
-  
-  <!-- Work around https://github.com/dotnet/sourcelink/issues/572
+    <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />
+
+    <!-- Work around https://github.com/dotnet/sourcelink/issues/572
   Remove once we build using an SDK that contains https://github.com/dotnet/sdk/pull/10613 -->
   <PropertyGroup>
-    <TargetFrameworkMonikerAssemblyAttributesPath>$([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)'))</TargetFrameworkMonikerAssemblyAttributesPath>
+      <TargetFrameworkMonikerAssemblyAttributesPath>$([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)'))</TargetFrameworkMonikerAssemblyAttributesPath>
   </PropertyGroup>
   <ItemGroup>
-    <EmbeddedFiles Include="$(GeneratedAssemblyInfoFile)"/>
+      <EmbeddedFiles Include="$(GeneratedAssemblyInfoFile)"/>
   </ItemGroup>
 
   <!-- We need this for the binplacing for testing assets.
   This should be removed at some point as it's brittle (harcodes versions and creates native-managed coupling). -->
   <Target Name="_PublishPackageReferences"
-          AfterTargets="PostBuildEvent"
-          Condition="$(NeedsPublishing) == 'true'"
-          DependsOnTargets="$(_BeforePublishNoBuildTargets);$(_CorePublishTargets)" />
+      AfterTargets="PostBuildEvent"
+      Condition="$(NeedsPublishing) == 'true'"
+      DependsOnTargets="$(_BeforePublishNoBuildTargets);$(_CorePublishTargets)" />
+
+  <Target Name="InvokeInstallRuntimes"
+      Condition="'$(IsUnitTestProject)' == 'true'"
+      BeforeTargets="RunTests;VSTest">
+      <MSBuild Projects="$(RepositoryEngineeringDir)InstallRuntimes.proj" Targets="InstallTestRuntimes" />
+  </Target>
 
 </Project>
index 325705a4bc654b9754e85ca71bc598ba03dc3bd4..ec5c3f3a31627ce69499d84fc41541887e78185f 100644 (file)
@@ -267,10 +267,14 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                         }
                         catch (ArgumentException ex)
                         {
-                            Trace.TraceError($"Module.Version FAILURE: '{versionToParse}' '{versionString}' {ex}");
+                            Trace.TraceError($"Module.GetVersion FAILURE: '{versionToParse}' '{versionString}' {ex}");
                         }
                     }
                 }
+                else
+                {
+                    Trace.TraceInformation($"Module.GetVersion no version string");
+                }
             }
 
             return versionData;
index 480c587d4b6d04b2a801fbaf5b710d3328256a4e..746ce1ecb11f242f7310857ec407c1d9b390b823 100644 (file)
@@ -294,6 +294,10 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                     {
                         buildId = elfFile.BuildID;
                     }
+                    else
+                    {
+                        Trace.TraceError($"GetBuildId: invalid ELF file {address:X16}");
+                    }
                 }
                 else if (Target.OperatingSystem == OSPlatform.OSX)
                 {
@@ -302,6 +306,10 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                     {
                         buildId = machOFile.Uuid;
                     }
+                    else
+                    {
+                        Trace.TraceError($"GetBuildId: invalid MachO file {address:X16}");
+                    }
                 }
             }
             catch (Exception ex) when (ex is InvalidVirtualAddressException || ex is BadInputFormatException || ex is IOException)
@@ -318,7 +326,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         /// <returns>version string or null</returns>
         protected string GetVersionString(ulong address)
         {
-            Stream stream = RawMemoryService.CreateMemoryStream();
+            Stream stream = MemoryService.CreateMemoryStream();
             try
             {
                 if (Target.OperatingSystem == OSPlatform.Linux)
@@ -328,7 +336,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                     {
                         foreach (ELFProgramHeader programHeader in elfFile.Segments.Select((segment) => segment.Header))
                         {
-                            uint flags = RawMemoryService.PointerSize == 8 ? programHeader.Flags : programHeader.Flags32;
+                            uint flags = MemoryService.PointerSize == 8 ? programHeader.Flags : programHeader.Flags32;
                             if (programHeader.Type == ELFProgramHeaderType.Load &&
                                (flags & (uint)ELFProgramHeaderAttributes.Writable) != 0)
                             {
@@ -340,6 +348,11 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                                 }
                             }
                         }
+                        Trace.TraceInformation($"GetVersionString: not found in ELF file {address:X16}");
+                    }
+                    else
+                    {
+                        Trace.TraceError($"GetVersionString: invalid ELF file {address:X16}");
                     }
                 }
                 else if (Target.OperatingSystem == OSPlatform.OSX)
@@ -361,6 +374,11 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                                 }
                             }
                         }
+                        Trace.TraceInformation($"GetVersionString: not found in MachO file {address:X16}");
+                    }
+                    else
+                    {
+                        Trace.TraceError($"GetVersionString: invalid MachO file {address:X16}");
                     }
                 }
                 else
index 194e1682537e0e51c62274ec6faa6f885be4b39d..435e38402926a820dd8d9ece701d530e996dbc55 100644 (file)
@@ -31,7 +31,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
             Regex regex = ModuleName is not null ? new Regex(ModuleName, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant) : null;
             foreach (ClrModule module in Runtime.EnumerateModules())
             {
-                if (regex is null || regex.IsMatch(Path.GetFileName(module.Name)))
+                if (regex is null || !string.IsNullOrEmpty(module.Name) && regex.IsMatch(Path.GetFileName(module.Name)))
                 {
                     if (Verbose)
                     {
index 7047b8b784762bd0f984efa333650cbed8d4e9c4..3db956e323f6c1cc3eaa001e57e610891df577b3 100644 (file)
@@ -38,7 +38,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
             foreach (IModule module in ModuleService.EnumerateModules().OrderBy((m) => m.ModuleIndex))
             {
                 totalSize += module.ImageSize;
-                if (regex is null || regex.IsMatch(Path.GetFileName(module.FileName)))
+                if (regex is null || !string.IsNullOrEmpty(module.FileName) && regex.IsMatch(Path.GetFileName(module.FileName)))
                 {
                     if (Verbose)
                     {
index 3b73de48c7a0b17523ec861a911f655c190533a2..8b05aa50b5e46816855a08dc18bba7c95ddc8b80 100644 (file)
@@ -3,21 +3,28 @@
 // See the LICENSE file in the project root for more information.
 
 using Microsoft.Diagnostics.DebugServices;
+using Microsoft.Diagnostics.Runtime;
+using System;
+using System.Collections.Immutable;
 using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
 
 namespace Microsoft.Diagnostics.ExtensionCommands
 {
     [Command(Name = "runtimes", Help = "List the runtimes in the target or change the default runtime.")]
-    public class RuntimesCommand: CommandBase
+    public class RuntimesCommand : CommandBase
     {
         public IRuntimeService RuntimeService { get; set; }
 
         public IContextService ContextService { get; set; }
 
-        [Option(Name = "-netfx", Help = "Switches to the desktop .NET Framework if exists.")]
+        public ITarget Target { get; set; }
+
+        [Option(Name = "--netfx", Aliases = new string[] { "-netfx", "-f" }, Help = "Switches to the desktop .NET Framework if exists.")]
         public bool NetFx { get; set; }
 
-        [Option(Name = "-netcore", Help = "Switches to the .NET Core runtime if exists.")]
+        [Option(Name = "--netcore", Aliases = new string[] { "-netcore", "-c" }, Help = "Switches to the .NET Core or .NET 5+ runtime if exists.")]
         public bool NetCore { get; set; }
 
         public override void Invoke()
@@ -51,8 +58,48 @@ namespace Microsoft.Diagnostics.ExtensionCommands
                     string current = displayStar ? (runtime == ContextService.GetCurrentRuntime() ? "*" : " ") : "";
                     Write(current);
                     Write(runtime.ToString());
+                    ClrInfo clrInfo = runtime.Services.GetService<ClrInfo>();
+                    if (clrInfo is not null)
+                    {
+                        unsafe
+                        {
+                            if (clrInfo.SingleFileRuntimeInfo.HasValue)
+                            {
+                                RuntimeInfo runtimeInfo = clrInfo.SingleFileRuntimeInfo.Value;
+                                WriteLine("    Signature:   {0}", Encoding.ASCII.GetString(runtimeInfo.Signature, RuntimeInfo.SignatureValueLength - 1));
+                                WriteLine("    Version:     {0}", runtimeInfo.Version);
+                                if (Target.OperatingSystem == OSPlatform.Windows)
+                                {
+                                    WriteLine("    Runtime:     {0}", GetWindowsIndex(runtimeInfo.RuntimeModuleIndex));
+                                    WriteLine("    DBI:         {0}", GetWindowsIndex(runtimeInfo.DbiModuleIndex));
+                                    WriteLine("    DAC:         {0}", GetWindowsIndex(runtimeInfo.DacModuleIndex));
+                                }
+                                else 
+                                {
+                                    WriteLine("    Runtime:     {0}",  GetUnixIndex(runtimeInfo.RuntimeModuleIndex));
+                                    WriteLine("    DBI:         {0}",  GetUnixIndex(runtimeInfo.DbiModuleIndex));
+                                    WriteLine("    DAC:         {0}",  GetUnixIndex(runtimeInfo.DacModuleIndex));
+                                }
+                            }
+                        }
+                    }
                 }
             }
         }
+
+        private unsafe string GetWindowsIndex(byte* index)
+        {
+            uint timeStamp = BitConverter.ToUInt32(new ReadOnlySpan<byte>(index + sizeof(byte), sizeof(uint)).ToArray(), 0);
+            uint fileSize = BitConverter.ToUInt32(new ReadOnlySpan<byte>(index + sizeof(byte) + sizeof(uint), sizeof(uint)).ToArray(), 0);
+            return string.Format("TimeStamp {0:X8} FileSize {1:X8}", timeStamp, fileSize);
+        }
+
+        private unsafe string GetUnixIndex(byte* index)
+        {
+            var buildId = new ReadOnlySpan<byte>(index + sizeof(byte), index[0]).ToArray().ToImmutableArray();
+            return string.Format("BuildId {0}", ToHex(buildId));
+        }
+
+        private string ToHex(ImmutableArray<byte> array) => string.Concat(array.Select((b) => b.ToString("x2")));
     }
 }
diff --git a/src/Microsoft.Diagnostics.TestHelpers/CharToLineConverter.cs b/src/Microsoft.Diagnostics.TestHelpers/CharToLineConverter.cs
new file mode 100644 (file)
index 0000000..c567124
--- /dev/null
@@ -0,0 +1,57 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Text;
+
+namespace Microsoft.Diagnostics.TestHelpers
+{
+    public sealed class CharToLineConverter
+    {
+        readonly Action<string> m_callback;
+        readonly StringBuilder m_text = new StringBuilder();
+
+        public CharToLineConverter(Action<string> callback)
+        {
+            m_callback = callback;
+        }
+
+        public void Input(byte[] buffer, int offset, int count)
+        {
+            for (int i = 0; i < count; i++) {
+                char c = (char)buffer[offset + i];
+                if (c == '\r') {
+                    continue;
+                }
+                if (c == '\n') {
+                    Flush();
+                }
+                else if (c == '\t' || (c >= (char)0x20 && c <= (char)127)) {
+                    m_text.Append(c);
+                }
+            }
+        }
+
+        public void Input(string text)
+        {
+            foreach (char c in text) {
+                if (c == '\r') {
+                    continue;
+                }
+                if (c == '\n') {
+                    Flush();
+                }
+                else if (c == '\t' || (c >= (char)0x20 && c <= (char)127)) {
+                    m_text.Append(c);
+                }
+            }
+        }
+
+        public void Flush()
+        {
+            m_callback(m_text.ToString());
+            m_text.Clear();
+        }
+    }
+}
index 288d90ec31733bd89ad70c553ba7ad1481684a7b..3dea8582d4f03baeb82b7c4bab928e9997d500e8 100644 (file)
@@ -12,11 +12,13 @@ namespace Microsoft.Diagnostics.TestHelpers
         public void WriteLine(string message)
         {
             Console.WriteLine(message);
+            Console.Out.Flush();
         }
 
         public void WriteLine(string format, params object[] args)
         {
             Console.WriteLine(format, args);
+            Console.Out.Flush();
         }
     }
 }
\ No newline at end of file
index c8225d885e15098254f9824e1564f5a8ec1561b8..63ef96bbbea73d5860b748f3fdea58f1c2cc7546 100644 (file)
@@ -3,8 +3,10 @@
 // See the LICENSE file in the project root for more information.
 
 using System;
+using System.Collections;
 using System.Collections.Generic;
 using System.IO;
+using System.Linq;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
@@ -190,6 +192,9 @@ namespace Microsoft.Diagnostics.TestHelpers
             ProcessRunner runner = new ProcessRunner(DotNetToolPath, args).
                 WithEnvironmentVariable("DOTNET_MULTILEVEL_LOOKUP", "0").
                 WithEnvironmentVariable("DOTNET_ROOT", Path.GetDirectoryName(DotNetToolPath)).
+                WithEnvironmentVariable("DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR", Path.GetDirectoryName(DotNetToolPath)).
+                WithEnvironmentVariable("DOTNET_INSTALL_DIR", Path.GetDirectoryName(DotNetToolPath)).
+                RemoveEnvironmentVariable("MSBuildSDKsPath").
                 WithWorkingDirectory(DebuggeeSolutionDirPath).
                 WithLog(output).
                 WithTimeout(TimeSpan.FromMinutes(10)).                    // restore can be painfully slow
@@ -231,10 +236,15 @@ namespace Microsoft.Diagnostics.TestHelpers
 
             output.WriteLine("Launching {0} {1}", DotNetToolPath, dotnetArgs);
             ProcessRunner runner = new ProcessRunner(DotNetToolPath, dotnetArgs).
-                      WithWorkingDirectory(DebuggeeProjectDirPath).
-                      WithLog(output).
-                      WithTimeout(TimeSpan.FromMinutes(10)). // a mac CI build of the modules debuggee is painfully slow :(
-                      WithExpectedExitCode(0);
+                WithEnvironmentVariable("DOTNET_MULTILEVEL_LOOKUP", "0").
+                WithEnvironmentVariable("DOTNET_ROOT", Path.GetDirectoryName(DotNetToolPath)).
+                WithEnvironmentVariable("DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR", Path.GetDirectoryName(DotNetToolPath)).
+                WithEnvironmentVariable("DOTNET_INSTALL_DIR", Path.GetDirectoryName(DotNetToolPath)).
+                RemoveEnvironmentVariable("MSBuildSDKsPath").
+                WithWorkingDirectory(DebuggeeProjectDirPath).
+                WithLog(output).
+                WithTimeout(TimeSpan.FromMinutes(10)). // a mac CI build of the modules debuggee is painfully slow :(
+                WithExpectedExitCode(0);
 
             if (OS.Kind != OSKind.Windows && Environment.GetEnvironmentVariable("HOME") == null)
             {
diff --git a/src/Microsoft.Diagnostics.TestHelpers/LoggingListener.cs b/src/Microsoft.Diagnostics.TestHelpers/LoggingListener.cs
new file mode 100644 (file)
index 0000000..1360a26
--- /dev/null
@@ -0,0 +1,55 @@
+using System;
+using System.Diagnostics;
+using Xunit.Abstractions;
+
+namespace Microsoft.Diagnostics.TestHelpers
+{
+    public class LoggingListener : TraceListener
+    {
+        private readonly CharToLineConverter _converter;
+
+        public static void EnableListener(ITestOutputHelper output, string name)
+        {
+            if (Trace.Listeners[name] == null) 
+            {
+                Trace.Listeners.Add(new LoggingListener(output, name));
+                Trace.AutoFlush = true;
+            }
+        }
+
+        public static void EnableConsoleListener(string name)
+        {
+            if (Trace.Listeners[name] == null) 
+            {
+                Trace.Listeners.Add(new LoggingListener(name));
+                Trace.AutoFlush = true;
+            }
+        }
+
+        private LoggingListener(ITestOutputHelper output, string name)
+            : base(name)
+        {
+            _converter = new CharToLineConverter((text) => {
+                output.WriteLine(text);
+            });
+        }
+
+        private LoggingListener(string name)
+            : base(name)
+        {
+            _converter = new CharToLineConverter((text) => {
+                Console.WriteLine(text);
+            });
+        }
+
+        public override void Write(string message)
+        {
+            _converter.Input(message);
+        }
+
+        public override void WriteLine(string message)
+        {
+            _converter.Input(message + Environment.NewLine);
+        }
+    }
+}
index 6f7dac27ab458a7d21b190ed0845be29b3594cd5..f2d83c5aa16b231ccd130087bd0df93f6b9f861c 100644 (file)
   </PropertyGroup>
   
   <ItemGroup>
+    <PackageReference Include="Microsoft.DotNet.RemoteExecutor" Version="$(MicrosoftDotNetRemoteExecutorVersion)" />
     <PackageReference Include="xunit" Version="$(XUnitVersion)" />
     <PackageReference Include="xunit.abstractions" Version="$(XUnitAbstractionsVersion)" />
   </ItemGroup>
+  
+  <ItemGroup>
+    <ProjectReference Include="$(MSBuildThisFileDirectory)..\Microsoft.Diagnostics.DebugServices.Implementation\Microsoft.Diagnostics.DebugServices.Implementation.csproj" />
+    <ProjectReference Include="$(MSBuildThisFileDirectory)..\Microsoft.Diagnostics.DebugServices\Microsoft.Diagnostics.DebugServices.csproj" />
+    <ProjectReference Include="$(MSBuildThisFileDirectory)..\Microsoft.Diagnostics.NETCore.Client\Microsoft.Diagnostics.NETCore.Client.csproj" />
+  </ItemGroup>
 </Project>
index 94dd27d5f97a58e2af47a9c84ee7a670e8bb8120..e5bca1393db6b6b73672e4b3cc75497ce4963abc 100644 (file)
@@ -115,6 +115,15 @@ namespace Microsoft.Diagnostics.TestHelpers
             get { lock (_lock) { return _replayCommand; } }
         }
 
+        public ProcessRunner RemoveEnvironmentVariable(string key)
+        {
+            lock (_lock)
+            {
+                _p.StartInfo.Environment.Remove(key);
+            }
+            return this;
+        }
+
         public ProcessRunner WithEnvironmentVariable(string key, string value)
         {
             lock (_lock)
@@ -378,7 +387,7 @@ namespace Microsoft.Diagnostics.TestHelpers
                 // can be thrown.
                 try
                 {
-                    p.Kill();
+                    p.Kill(entireProcessTree: true);
                 }
                 catch (InvalidOperationException) { }
             }
diff --git a/src/Microsoft.Diagnostics.TestHelpers/RemoteExecutorHelper.cs b/src/Microsoft.Diagnostics.TestHelpers/RemoteExecutorHelper.cs
new file mode 100644 (file)
index 0000000..184504e
--- /dev/null
@@ -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.
+
+using Microsoft.Diagnostics.NETCore.Client;
+using Microsoft.DotNet.RemoteExecutor;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit.Abstractions;
+using Xunit.Sdk;
+
+namespace Microsoft.Diagnostics.TestHelpers
+{
+    public static class RemoteExecutorHelper
+    {
+        public static async Task<int> RemoteInvoke(ITestOutputHelper output, TestConfiguration config, TimeSpan timeout, string dumpPath, Func<string, Task<int>> method)
+        {
+            RemoteInvokeOptions options = new()
+            {
+                StartInfo = new ProcessStartInfo() { RedirectStandardOutput = true, RedirectStandardError = true }
+            };
+            // The remoteInvokeHandle is NOT disposed (i.e. with a using) here because the RemoteExecutor dispose code uses an older (1.x) version
+            // of clrmd that conflicts with the 2.0 version diagnostics is using and throws the exception:
+            //
+            // "Method not found: 'Microsoft.Diagnostics.Runtime.DataTarget Microsoft.Diagnostics.Runtime.DataTarget.AttachToProcess(Int32, UInt32)'."
+            //
+            // When RemoteExecutor is fixed the "using" can be added and the GC.SuppressFinalize be removed.
+            RemoteInvokeHandle remoteInvokeHandle = RemoteExecutor.Invoke(method, config.Serialize(), options);
+            GC.SuppressFinalize(remoteInvokeHandle);
+            try
+            {
+                Task stdOutputTask = WriteStreamToOutput(remoteInvokeHandle.Process.StandardOutput, output);
+                Task stdErrorTask = WriteStreamToOutput(remoteInvokeHandle.Process.StandardError, output);
+                Task outputTasks = Task.WhenAll(stdErrorTask, stdOutputTask);
+
+                Task processExit = Task.Factory.StartNew(() => remoteInvokeHandle.Process.WaitForExit(), TaskCreationOptions.LongRunning);
+                Task timeoutTask = Task.Delay(timeout);
+                Task completedTask = await Task.WhenAny(outputTasks, processExit, timeoutTask);
+                if (completedTask == timeoutTask)
+                {
+                    if (!string.IsNullOrEmpty(dumpPath))
+                    {
+                        output.WriteLine($"RemoteExecutorHelper.RemoteInvoke timed out: writing dump to {dumpPath}");
+                        DiagnosticsClient client = new(remoteInvokeHandle.Process.Id);
+                        try
+                        {
+                            await client.WriteDumpAsync(DumpType.WithHeap, dumpPath, WriteDumpFlags.None, CancellationToken.None);
+                        }
+                        catch (Exception ex) when (ex is ArgumentException || ex is UnsupportedCommandException || ex is ServerErrorException)
+                        {
+                            output.WriteLine($"RemoteExecutorHelper.RemoteInvoke: writing dump FAILED {ex}");
+                        }
+                    }
+                    throw new XunitException("RemoteExecutorHelper.RemoteInvoke timed out");
+                }
+                else
+                {
+                    return remoteInvokeHandle.ExitCode;
+                }
+            }
+            finally
+            { 
+                if (remoteInvokeHandle.Process != null)
+                {
+                    try
+                    {
+                        output.WriteLine($"RemoteExecutorHelper.RemoteInvoke: killing process {remoteInvokeHandle.Process.Id}");
+                        remoteInvokeHandle.Process.Kill(entireProcessTree: true);
+                    }
+                    catch 
+                    {
+                    }
+                    remoteInvokeHandle.Process.Dispose();
+                    remoteInvokeHandle.Process = null;
+                }
+            }
+        }
+
+        public static async Task RemoteInvoke(ITestOutputHelper output, Action testCase)
+        {
+            var options = new RemoteInvokeOptions()
+            {
+                StartInfo = new ProcessStartInfo() { RedirectStandardOutput = true, RedirectStandardError = true }
+            };
+            using RemoteInvokeHandle remoteInvokeHandle = RemoteExecutor.Invoke(testCase, options);
+
+            Task stdOutputTask = WriteStreamToOutput(remoteInvokeHandle.Process.StandardOutput, output);
+            Task stdErrorTask = WriteStreamToOutput(remoteInvokeHandle.Process.StandardError, output);
+            await Task.WhenAll(stdErrorTask, stdOutputTask);
+        }
+
+        private static Task WriteStreamToOutput(StreamReader reader, ITestOutputHelper output)
+        {
+            return Task.Factory.StartNew(creationOptions: TaskCreationOptions.LongRunning, function: async () => {
+                try
+                {
+                    while (!reader.EndOfStream)
+                    {
+                        string line = await reader.ReadLineAsync();
+                        output.WriteLine(line);
+                    }
+                }
+                catch (ObjectDisposedException)
+                {
+                    output.WriteLine("Failed to collect remote process's output");
+                }
+            });
+        }
+    }
+}
index e68dde4bffe13cca1e49746fba5642f6a4dda54b..ece382ad0daa231388734d45ac99f69386e1b147 100644 (file)
@@ -77,6 +77,8 @@ namespace Microsoft.Diagnostics.TestHelpers
                 ["TempPath"] = Path.GetTempPath(),
                 ["WorkingDir"] = GetInitialWorkingDir(),
                 ["OS"] = OS.Kind.ToString(),
+                ["IsAlpine"] = OS.IsAlpine.ToString().ToLowerInvariant(),
+                ["TargetRid"] = GetRid(),
                 ["TargetArchitecture"] = OS.TargetArchitecture.ToString().ToLowerInvariant(),
                 ["NuGetPackageCacheDir"] = nugetPackages
             };
@@ -88,6 +90,19 @@ namespace Microsoft.Diagnostics.TestHelpers
             Configurations = configs.Select(c => new TestConfiguration(c)).ToList();
         }
 
+        static string GetRid()
+        {
+            string os = OS.Kind switch
+            {
+                OSKind.Linux => OS.IsAlpine ? "linux-musl" : "linux",
+                OSKind.OSX => "osx",
+                OSKind.Windows => "win",
+                _ => throw new PlatformNotSupportedException(),
+            };
+            string architecture = OS.TargetArchitecture.ToString().ToLowerInvariant();
+            return $"{os}-{architecture}";
+        }
+
         Dictionary<string, string>[] ParseConfigFile(string path, Dictionary<string, string>[] templates)
         {
             XDocument doc = XDocument.Load(path);
@@ -388,6 +403,30 @@ namespace Microsoft.Diagnostics.TestHelpers
             _configStringView = GetStringViewWithVersion(RuntimeFrameworkVersion); 
         }
 
+        public string Serialize()
+        {
+            List<XElement> nodes = new();
+            foreach (KeyValuePair<string, string> keyvalue in _settings)
+            {
+                nodes.Add(new XElement(keyvalue.Key, keyvalue.Value));
+            }
+            XElement root = new("Configuration", nodes.ToArray());
+            TextWriter writer = new StringWriter();
+            root.Save(writer);
+            return writer.ToString();
+        }
+
+        public static TestConfiguration Deserialize(string xml)
+        {
+            XElement root = XElement.Parse(xml);
+            Dictionary<string, string> settings = new();
+            foreach (XElement child in root.Elements())
+            {
+                settings.Add(child.Name.LocalName, child.Value);
+            }
+            return new TestConfiguration(settings);
+        }
+
         private string GetTruncatedRuntimeFrameworkVersion()
         {
             string version = RuntimeFrameworkVersion;
diff --git a/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestDataReader.cs b/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestDataReader.cs
new file mode 100644 (file)
index 0000000..192fe06
--- /dev/null
@@ -0,0 +1,338 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using System.Xml.Linq;
+using Xunit;
+
+namespace Microsoft.Diagnostics.TestHelpers
+{
+    public class TestDataReader
+    {
+        /// <summary>
+        /// Test data value
+        /// </summary>
+        public class Value
+        {
+            private readonly object _value;
+
+            internal Value(string valueString)
+            {
+                _value = valueString;
+            }
+
+            internal Value(ImmutableArray<ImmutableDictionary<string, Value>> values)
+            {
+                _value = values;
+            }
+
+            /// <summary>
+            /// Returns true if sub values
+            /// </summary>
+            public bool IsSubValue => _value is ImmutableArray<ImmutableDictionary<string, Value>>;
+
+            /// <summary>
+            /// Return the sub values for nested test data
+            /// </summary>
+            public ImmutableArray<ImmutableDictionary<string, Value>> Values
+            {
+                get { return _value is ImmutableArray<ImmutableDictionary<string, Value>> values ? values : ImmutableArray<ImmutableDictionary<string, Value>>.Empty; }
+            }
+
+            /// <summary>
+            /// Get the test data value as type T
+            /// </summary>
+            public T GetValue<T>()
+            {
+                return (T)GetValue(typeof(T));
+            }
+
+            /// <summary>
+            /// Get the test data value as "type"
+            /// </summary>
+            public object GetValue(Type type)
+            {
+                object value = _value;
+                if (value is string valueString)
+                {
+                    GetValue(type, valueString, ref value);
+                }
+                return value;
+            }
+
+            /// <summary>
+            /// Convert test data string to value
+            /// </summary>
+            /// <param name="type">type to convert to</param>
+            /// <param name="valueString">test data string</param>
+            /// <param name="result">resulting object</param>
+            public static void GetValue(Type type, string valueString, ref object result)
+            {
+                valueString = valueString.Trim();
+                if (type == typeof(string))
+                {
+                    result = valueString ?? "";
+                }
+                else if (type == typeof(bool))
+                {
+                    switch (valueString.ToLowerInvariant())
+                    {
+                        case "true":
+                            result = true;
+                            break;
+                        case "false":
+                            result = false;
+                            break;
+                    }
+                }
+                else if (type.IsEnum)
+                {
+                    result = Enum.Parse(type, valueString);
+                }
+                else if (type.IsPrimitive)
+                {
+                    NumberStyles style = valueString.StartsWith("0x") ? NumberStyles.HexNumber : NumberStyles.Integer;
+                    if (ulong.TryParse(valueString.Replace("0x", ""), style, CultureInfo.InvariantCulture, out ulong integerValue))
+                    {
+                        result = Convert.ChangeType(integerValue, type);
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Test data file version
+        /// </summary>
+        public readonly Version Version;
+
+        /// <summary>
+        /// Target test data
+        /// </summary>
+        public readonly ImmutableDictionary<string, Value> Target;
+
+        /// <summary>
+        /// Shortcut to the module test data
+        /// </summary>
+        public readonly ImmutableArray<ImmutableDictionary<string, Value>> Modules;
+
+        /// <summary>
+        /// Shortcut  to the thread test data
+        /// </summary>
+        public readonly ImmutableArray<ImmutableDictionary<string, Value>> Threads;
+
+        /// <summary>
+        /// Shortcut to the runtime test data
+        /// </summary>
+        public readonly ImmutableArray<ImmutableDictionary<string, Value>> Runtimes;
+
+        /// <summary>
+        /// Open the test data xml file
+        /// </summary>
+        /// <param name="testDataFile"></param>
+        public TestDataReader(string testDataFile)
+        {
+            XDocument doc = XDocument.Load(testDataFile);
+            XElement root = doc.Root;
+            Assert.Equal("TestData", root.Name);
+            foreach (XElement child in root.Elements())
+            {
+                switch (child.Name.LocalName)
+                {
+                    case "Version":
+                        Version = Version.Parse(child.Value);
+                        break;
+                    case "Target":
+                        Target = Build(child);
+                        break;
+                }
+            }
+            Modules = Target["Modules"].Values;
+            Threads = Target["Threads"].Values;
+            Runtimes = Target["Runtimes"].Values;
+        }
+
+        private static ImmutableDictionary<string, Value> Build(XElement node)
+        {
+            var members = new Dictionary<string, Value>();
+            foreach (XElement dataNode in node.Elements())
+            {
+                string name = dataNode.Name.LocalName;
+                if (dataNode.HasElements)
+                {
+                    var items = new List<ImmutableDictionary<string, Value>>();
+                    foreach (XElement subValue in dataNode.Elements())
+                    {
+                        if (subValue.HasElements)
+                        {
+                            // Has multiple elements (i.e. Modules, Threads, Runtimes, 
+                            // etc). Assumes the same name for each entry.
+                            items.Add(Build(subValue));
+                        }
+                        else
+                        {
+                            // Only has sub members (i.e. RuntimeModule, etc.)
+                            items.Add(Build(dataNode));
+                            break;
+                        }
+                    }
+                    members.Add(name, new Value(items.ToImmutableArray()));
+                }
+                else
+                {
+                    members.Add(name, new Value(dataNode.Value));
+                }
+            }
+            return members.ToImmutableDictionary();
+        }
+    }
+
+    public static class TestDataExtensions
+    {
+        /// <summary>
+        /// Helper function to get a test data value 
+        /// </summary>
+        /// <typeparam name="T">type to convert test data value</typeparam>
+        /// <param name="values">values collection to lookup name</param>
+        /// <param name="name">value name</param>
+        /// <param name="value">result value of type T</param>
+        /// <returns></returns>
+        public static bool TryGetValue<T>(
+            this ImmutableDictionary<string, TestDataReader.Value> values, string name, out T value)
+        {
+            if (values.TryGetValue(name, out TestDataReader.Value testValue))
+            {
+                value = testValue.GetValue<T>();
+                return true;
+            }
+            value = default;
+            return false;
+        }
+
+        /// <summary>
+        /// Finds the match item (i.e. IModule, IThread, etc.) in the test data.
+        /// </summary>
+        /// <typeparam name="T">field or property type</typeparam>
+        /// <param name="items">Modules, Threads, Registers, etc. test data</param>
+        /// <param name="propety">name of property to use for search</param>
+        /// <param name="propertyValue"></param>
+        /// <returns>test data values</returns>
+        public static ImmutableDictionary<string, TestDataReader.Value> Find<T>(
+            this ImmutableArray<ImmutableDictionary<string, TestDataReader.Value>> items, string propety, T propertyValue)
+            where T : IComparable
+        {
+            foreach (var item in items)
+            {
+                TestDataReader.Value value = item[propety];
+                if (propertyValue.CompareTo(value.GetValue<T>()) == 0)
+                {
+                    return item;
+                }
+            }
+            return default;
+        }
+
+        /// <summary>
+        /// Compares the test data values with the properties in the instance with the same name. This is
+        /// used to compare ITarget, IModule, IThread, RegiserInfo instances to the test data.
+        /// </summary>
+        /// <param name="values">test data for the item</param>
+        /// <param name="instance">object to compare</param>
+        public static void CompareMembers(
+            this ImmutableDictionary<string, TestDataReader.Value> values, object instance)
+        {
+            foreach (KeyValuePair<string, TestDataReader.Value> testData in values)
+            {
+                MemberInfo[] members = instance.GetType().GetMember(
+                    testData.Key,
+                    MemberTypes.Field | MemberTypes.Property | MemberTypes.Method,
+                    BindingFlags.Public | BindingFlags.Instance);
+
+                if (members.Length > 0)
+                {
+                    MemberInfo member = members.Single();
+                    object memberValue = null;
+                    Type memberType = null;
+
+                    switch (member.MemberType)
+                    {
+                        case MemberTypes.Property:
+                            memberValue = ((PropertyInfo)member).GetValue(instance);
+                            memberType = ((PropertyInfo)member).PropertyType;
+                            break;
+                        case MemberTypes.Field:
+                            memberValue = ((FieldInfo)member).GetValue(instance);
+                            memberType = ((FieldInfo)member).FieldType;
+                            break;
+                        case MemberTypes.Method:
+                            if (((MethodInfo)member).GetParameters().Length == 0)
+                            {
+                                memberValue = ((MethodInfo)member).Invoke(instance, null);
+                                memberType = ((MethodInfo)member).ReturnType;
+                            }
+                            break;
+                    }
+                    if (memberType != null)
+                    {
+                        if (testData.Value.IsSubValue)
+                        {
+                            Trace.TraceInformation($"CompareMembers {testData.Key} sub value:");
+                            CompareMembers(testData.Value.Values.Single(), memberValue);
+                        }
+                        else
+                        {
+                            Type nullableType = Nullable.GetUnderlyingType(memberType);
+                            memberType = nullableType ?? memberType;
+
+                            if (nullableType != null && memberValue == null)
+                            {
+                                memberValue = string.Empty;
+                            }
+                            else if (memberType == typeof(string))
+                            {
+                                memberValue ??= string.Empty;
+                            }
+                            else if (memberValue is ImmutableArray<byte> buildId)
+                            {
+                                memberType = typeof(string);
+                                memberValue = !buildId.IsDefaultOrEmpty ? string.Concat(buildId.Select((b) => b.ToString("x2"))) : string.Empty;
+                            }
+                            else if (!memberType.IsPrimitive && !memberType.IsEnum)
+                            {
+                                memberType = typeof(string);
+                                memberValue = memberValue?.ToString() ?? string.Empty;
+                            }
+                            object testDataValue = testData.Value.GetValue(memberType);
+                            Trace.TraceInformation($"CompareMembers {testData.Key}: expected '{testDataValue}' actual '{memberValue}'");
+
+                            // Disable checking the VersionData property because downloading the necessary binary to map into the address is unreliable
+                            // See issue: https://github.com/dotnet/diagnostics/issues/2955
+                            if (testData.Key == "VersionData")
+                            {
+                                if (!object.Equals(testDataValue, memberValue))
+                                {
+                                    Trace.TraceError($"CompareMembers VersionData: expected '{testDataValue}' != actual '{memberValue}'");
+                                }
+                            }
+                            else
+                            {
+                                Assert.Equal(testDataValue, memberValue);
+                            }
+                        }
+                    }
+                    else 
+                    { 
+                        Trace.TraceWarning($"CompareMembers {testData.Key} member not found");
+                        return;
+                    }
+                }
+                else
+                {
+                    Trace.TraceWarning($"CompareMembers {testData.Key} not found");
+                }
+            }
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestDataWriter.cs b/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestDataWriter.cs
new file mode 100644 (file)
index 0000000..5e6b284
--- /dev/null
@@ -0,0 +1,261 @@
+using Microsoft.Diagnostics.DebugServices;
+using System;
+using System.Collections.Immutable;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Xml.Linq;
+
+namespace Microsoft.Diagnostics.TestHelpers
+{
+    public class TestDataWriter
+    {
+        public readonly XElement Root;
+        public readonly XElement Target;
+
+        /// <summary>
+        /// Write a test data file from the target
+        /// </summary>
+        public TestDataWriter()
+        {
+            Root = new XElement("TestData");
+            Root.Add(new XElement("Version", "1.0.0"));
+            Target = new XElement("Target");
+            Root.Add(Target);
+        }
+
+        public void Build(ITarget target)
+        {
+            AddMembers(Target, typeof(ITarget), target, nameof(ITarget.Id), nameof(ITarget.GetTempDirectory));
+
+            var modulesElement = new XElement("Modules");
+            Target.Add(modulesElement);
+
+            var moduleService = target.Services.GetService<IModuleService>();
+            string runtimeModuleName = target.GetPlatformModuleName("coreclr");
+            foreach (IModule module in moduleService.EnumerateModules())
+            {
+                var moduleElement = new XElement("Module");
+                modulesElement.Add(moduleElement);
+                AddModuleMembers(moduleElement, module, runtimeModuleName);
+            }
+
+            var threadsElement = new XElement("Threads");
+            Target.Add(threadsElement);
+
+            var threadService = target.Services.GetService<IThreadService>();
+            var registerIndexes = new int[] { threadService.InstructionPointerIndex, threadService.StackPointerIndex, threadService.FramePointerIndex };
+            foreach (IThread thread in threadService.EnumerateThreads())
+            {
+                var threadElement = new XElement("Thread");
+                threadsElement.Add(threadElement);
+                AddMembers(threadElement, typeof(IThread), thread, nameof(IThread.ThreadIndex), nameof(IThread.GetThreadContext));
+
+                var registersElement = new XElement("Registers");
+                threadElement.Add(registersElement);
+                foreach (int registerIndex in registerIndexes)
+                {
+                    var registerElement = new XElement("Register");
+                    registersElement.Add(registerElement);
+
+                    if (threadService.TryGetRegisterInfo(registerIndex, out RegisterInfo info))
+                    {
+                        AddMembers(registerElement, typeof(RegisterInfo), info, nameof(Object.ToString), nameof(Object.GetHashCode));
+                    }
+                    if (thread.TryGetRegisterValue(registerIndex, out ulong value))
+                    {
+                        registerElement.Add(new XElement("Value", $"0x{value:X16}"));
+                    }
+                }
+            }
+
+            var runtimesElement = new XElement("Runtimes");
+            Target.Add(runtimesElement);
+
+            var runtimeService = target.Services.GetService<IRuntimeService>();
+            foreach (IRuntime runtime in runtimeService.EnumerateRuntimes())
+            {
+                var runtimeElement = new XElement("Runtime");
+                runtimesElement.Add(runtimeElement);
+                AddMembers(runtimeElement, typeof(IRuntime), runtime, nameof(IRuntime.GetDacFilePath), nameof(IRuntime.GetDbiFilePath));
+
+                var runtimeModuleElement = new XElement("RuntimeModule");
+                runtimeElement.Add(runtimeModuleElement);
+                AddModuleMembers(runtimeModuleElement, runtime.RuntimeModule, symbolModuleName: null);
+            }
+        }
+
+        public void Write(string testDataFile)
+        {
+            File.WriteAllText(testDataFile, Root.ToString());
+        }
+
+        private void AddModuleMembers(XElement element, IModule module, string symbolModuleName)
+        {
+            AddMembers(element, typeof(IModule), module, nameof(IModule.ModuleIndex), nameof(IModule.PdbFileInfos), nameof(IModule.VersionString));
+
+            if (symbolModuleName != null && IsModuleEqual(module, symbolModuleName))
+            {
+                IExportSymbols exportSymbols = module.Services.GetService<IExportSymbols>();
+                if (exportSymbols is not null)
+                {
+                    XElement exportSymbolsElement = null;
+
+                    string symbol1 = "coreclr_initialize";
+                    if (exportSymbols.TryGetSymbolAddress(symbol1, out ulong offset1))
+                    {
+                        XElement symbolElement = AddExportSymbolSection();
+                        symbolElement.Add(new XElement("Name", symbol1));
+                        symbolElement.Add(new XElement("Value", ToHex(offset1)));
+                    }
+                    string symbol2 = "coreclr_execute_assembly";
+                    if (exportSymbols.TryGetSymbolAddress(symbol2, out ulong offset2))
+                    {
+                        XElement symbolElement = AddExportSymbolSection();
+                        symbolElement.Add(new XElement("Name", symbol2));
+                        symbolElement.Add(new XElement("Value", ToHex(offset2)));
+                    }
+
+                    XElement AddExportSymbolSection()
+                    {
+                        if (exportSymbolsElement == null)
+                        {
+                            exportSymbolsElement = new XElement("ExportSymbols");
+                            element.Add(exportSymbolsElement);
+                        }
+                        var symbolElement = new XElement("Symbol");
+                        exportSymbolsElement.Add(symbolElement);
+                        return symbolElement;
+                    }
+                }
+
+                IModuleSymbols moduleSymbols = module.Services.GetService<IModuleSymbols>();
+                if (moduleSymbols is not null)
+                {
+                    XElement symbolsElement = null;
+
+                    string symbol1 = "coreclr_initialize";
+                    if (moduleSymbols.TryGetSymbolAddress(symbol1, out ulong offset1))
+                    {
+                        XElement symbolElement = AddExportSymbolSection();
+                        symbolElement.Add(new XElement("Name", symbol1));
+                        symbolElement.Add(new XElement("Value", ToHex(offset1)));
+                        symbolElement.Add(new XElement("Displacement", "0"));
+                    }
+
+                    XElement AddExportSymbolSection()
+                    {
+                        if (symbolsElement == null)
+                        {
+                            symbolsElement = new XElement("Symbols");
+                            element.Add(symbolsElement);
+                        }
+                        var symbolElement = new XElement("Symbol");
+                        symbolsElement.Add(symbolElement);
+                        return symbolElement;
+                    }
+                }
+            }
+        }
+
+        private void AddMembers(XElement element, Type type, object instance, params string[] membersToSkip)
+        {
+            MemberInfo[] members = type.GetMembers(BindingFlags.Public | BindingFlags.Instance);
+            foreach (MemberInfo member in members)
+            {
+                if (membersToSkip.Any((skip) => member.Name == skip)) {
+                    continue;
+                }
+                string result = null;
+                object memberValue = null;
+                Type memberType = null;
+
+                switch (member.MemberType)
+                {
+                    case MemberTypes.Property:
+                        memberValue = ((PropertyInfo)member).GetValue(instance);
+                        memberType = ((PropertyInfo)member).PropertyType;
+                        break;
+                    case MemberTypes.Field:
+                        memberValue = ((FieldInfo)member).GetValue(instance);
+                        memberType = ((FieldInfo)member).FieldType;
+                        break;
+                    case MemberTypes.Method:
+                        MethodInfo methodInfo = (MethodInfo)member;
+                        if (!methodInfo.IsSpecialName && methodInfo.GetParameters().Length == 0 && methodInfo.ReturnType != typeof(void))
+                        {
+                            memberValue = ((MethodInfo)member).Invoke(instance, null);
+                            memberType = ((MethodInfo)member).ReturnType;
+                        }
+                        break;
+                }
+                if (memberType != null)
+                {
+                    Type nullableType = Nullable.GetUnderlyingType(memberType);
+                    memberType = nullableType ?? memberType;
+
+                    if (nullableType != null && memberValue == null)
+                    {
+                        result = "";
+                    }
+                    else if (memberType == typeof(string))
+                    {
+                        result = (string)memberValue ?? "";
+                    }
+                    else if (memberType == typeof(bool))
+                    {
+                        result = (bool)memberValue ? "true" : "false";
+                    }
+                    else if (memberValue is ImmutableArray<byte> buildId)
+                    {
+                        if (!buildId.IsDefaultOrEmpty)
+                        {
+                            result = string.Concat(buildId.Select((b) => b.ToString("x2")));
+                        }
+                    }
+                    else if (memberType.IsEnum)
+                    {
+                        result = memberValue.ToString();
+                    }
+                    else if (memberType.IsPrimitive)
+                    {
+                        if (memberType == typeof(short) || memberType == typeof(int) || memberType == typeof(long))
+                        {
+                            result = memberValue.ToString();
+                        }
+                        else
+                        {
+                            int digits = Marshal.SizeOf(memberType) * 2;
+                            result = string.Format($"0x{{0:X{digits}}}", memberValue);
+                        }
+                    }
+                    else if (memberType.IsValueType || memberType == typeof(VersionData) || memberType == typeof(PdbFileInfo))
+                    {
+                        result = memberValue?.ToString();
+                    }
+                }
+                if (result != null)
+                {
+                    element.Add(new XElement(member.Name, result));
+                }
+            }
+        }
+
+        private string ToHex<T>(T value) where T : struct 
+        {
+            int digits = Marshal.SizeOf(typeof(T)) * 2;
+            return string.Format($"0x{{0:X{digits}}}", value);
+        }
+
+        private bool IsModuleEqual(IModule module, string moduleName)
+        {
+            if (module.Target.OperatingSystem == OSPlatform.Windows) {
+                return StringComparer.OrdinalIgnoreCase.Equals(Path.GetFileName(module.FileName), moduleName);
+            }
+            else {
+                return string.Equals(Path.GetFileName(module.FileName), moduleName);
+            }
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestDump.cs b/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestDump.cs
new file mode 100644 (file)
index 0000000..33a64dd
--- /dev/null
@@ -0,0 +1,71 @@
+using Microsoft.Diagnostics.DebugServices;
+using Microsoft.Diagnostics.DebugServices.Implementation;
+using Microsoft.Diagnostics.Runtime;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Diagnostics.TestHelpers
+{
+    public class TestDump : TestHost, IHost
+    {
+        private readonly ServiceProvider _serviceProvider;
+        private readonly ContextService _contextService;
+        private readonly SymbolService _symbolService;
+        private DataTarget _dataTarget;
+        private int _targetIdFactory;
+
+        public TestDump(TestConfiguration config)
+            : base(config)
+        {
+            _serviceProvider = new ServiceProvider();
+            _contextService = new ContextService(this);
+            _symbolService = new SymbolService(this);
+            _serviceProvider.AddService<IContextService>(_contextService);
+            _serviceProvider.AddService<ISymbolService>(_symbolService);
+
+            // Automatically enable symbol server support
+            _symbolService.AddSymbolServer(msdl: true, symweb: false, symbolServerPath: null, authToken: null, timeoutInMinutes: 0);
+            _symbolService.AddCachePath(_symbolService.DefaultSymbolCache);
+        }
+
+        protected override ITarget GetTarget()
+        {
+            _dataTarget = DataTarget.LoadDump(DumpFile);
+
+            OSPlatform targetPlatform = _dataTarget.DataReader.TargetPlatform;
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
+                targetPlatform = OSPlatform.OSX;
+            }
+            _symbolService.AddDirectoryPath(Path.GetDirectoryName(DumpFile));
+            return new TargetFromDataReader(_dataTarget.DataReader, targetPlatform, this, _targetIdFactory++, DumpFile);
+        }
+
+        #region IHost
+
+        public IServiceEvent OnShutdownEvent { get; } = new ServiceEvent();
+
+        HostType IHost.HostType => HostType.DotnetDump;
+
+        IServiceProvider IHost.Services => _serviceProvider;
+
+        IEnumerable<ITarget> IHost.EnumerateTargets() => Target != null ? new ITarget[] { Target } : Array.Empty<ITarget>();
+
+        void IHost.DestroyTarget(ITarget target)
+        {
+            if (target == null) {
+                throw new ArgumentNullException(nameof(target));
+            }
+            if (target == Target)
+            {
+                _contextService.ClearCurrentTarget();
+                if (target is IDisposable disposable) {
+                    disposable.Dispose();
+                }
+            }
+        }
+
+        #endregion
+    }
+}
diff --git a/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestHost.cs b/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestHost.cs
new file mode 100644 (file)
index 0000000..92ba060
--- /dev/null
@@ -0,0 +1,50 @@
+using Microsoft.Diagnostics.DebugServices;
+
+namespace Microsoft.Diagnostics.TestHelpers
+{
+    public abstract class TestHost
+    {
+        private TestDataReader _testData;
+        private ITarget _target;
+
+        public readonly TestConfiguration Config;
+
+        public TestHost(TestConfiguration config)
+        {
+            Config = config;
+        }
+
+        public TestDataReader TestData
+        {
+            get
+            {
+                _testData ??= new TestDataReader(TestDataFile);
+                return _testData;
+            }
+        }
+
+        public ITarget Target
+        {
+            get
+            {
+                _target ??= GetTarget();
+                return _target;
+            }
+        }
+
+        public bool IsTestDbgEng => Config.AllSettings.TryGetValue("TestDbgEng", out string value) && value == "true";
+
+        protected abstract ITarget GetTarget();
+
+        public string DumpFile => TestConfiguration.MakeCanonicalPath(Config.AllSettings["DumpFile"]);
+
+        public string TestDataFile => TestConfiguration.MakeCanonicalPath(Config.AllSettings["TestDataFile"]);
+
+        public override string ToString() => DumpFile;
+    }
+
+    public static class TestHostExtensions
+    {
+        public static bool IsTestDbgEng(this TestConfiguration config) => config.AllSettings.TryGetValue("TestDbgEng", out string value) && value == "true";
+    }
+}
index a9fd5dc9f5fcefaab0d37e60ef88a2fbdd42598c..d969df20ebceb150164bf174b4b248068b4a185d 100644 (file)
@@ -26,9 +26,9 @@ namespace SOS
             OSX = 3,
         };
 
-        internal static Guid IID_IDebuggerServices = new Guid("B4640016-6CA0-468E-BA2C-1FFF28DE7B72");
+        private static Guid IID_IDebuggerServices = new Guid("B4640016-6CA0-468E-BA2C-1FFF28DE7B72");
 
-        private ref readonly IDebuggerServicesVTable VTable => ref Unsafe.AsRef<IDebuggerServicesVTable >(_vtable);
+        private ref readonly IDebuggerServicesVTable VTable => ref Unsafe.AsRef<IDebuggerServicesVTable>(_vtable);
 
         private readonly HostType _hostType;
 
@@ -166,7 +166,8 @@ namespace SOS
             fixed (byte* versionBufferPtr = versionBuffer)
             {
                 HResult hr = VTable.GetModuleVersionInformation(Self, (uint)index, 0, getVersionInfoPtr, versionBufferPtr, (uint)versionBufferSize, null);
-                if (hr == HResult.S_OK) {
+                if (hr == HResult.S_OK)
+                {
                     fileInfo = *((VS_FIXEDFILEINFO*)versionBufferPtr);
                 }
                 return hr;
@@ -184,7 +185,8 @@ namespace SOS
             fixed (byte* versionBufferPtr = versionBuffer)
             {
                 int hr = VTable.GetModuleVersionInformation(Self, (uint)index, 0, getVersionStringPtr, versionBufferPtr, (uint)versionBuffer.Length, null);
-                if (hr == HResult.S_OK) {
+                if (hr == HResult.S_OK)
+                {
                     version = Marshal.PtrToStringAnsi(new IntPtr(versionBufferPtr));
                 }
                 return hr;
@@ -298,7 +300,8 @@ namespace SOS
                             if (_hostType == HostType.DbgEng)
                             {
                                 int index = symbol.IndexOf('!');
-                                if (index != -1) {
+                                if (index != -1)
+                                {
                                     symbol = symbol.Remove(0, index + 1);
                                 }
                             }
@@ -323,32 +326,32 @@ namespace SOS
                 return VTable.GetOffsetBySymbol(Self, moduleIndex, symbolPtr, out address);
             }
         }
-    }
 
-    [StructLayout(LayoutKind.Sequential)]
-    internal readonly unsafe struct IDebuggerServicesVTable
-    {
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, out DebuggerServices.OperatingSystem, HResult> GetOperatingSystem;
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, out DEBUG_CLASS, out DEBUG_CLASS_QUALIFIER, HResult> GetDebuggeeType;
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, out IMAGE_FILE_MACHINE, HResult> GetExecutingProcessorType;
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, byte*, byte*, IntPtr*, int, HResult> AddCommand;
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, DEBUG_OUTPUT, byte*, void> OutputString;
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, ulong, byte*, uint, out int, HResult> ReadVirtual;
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, ulong, byte*, uint, out int, HResult> WriteVirtual;
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, out uint, out uint, HResult> GetNumberModules;
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, uint, ulong, byte*, uint, out uint, byte*, uint, uint*, byte*, uint, uint*, HResult> GetModuleNames;
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, uint, out ulong, out ulong, out uint, out uint, HResult> GetModuleInfo;
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, uint, ulong, byte*, byte*, uint, uint*, HResult> GetModuleVersionInformation; 
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, out uint, HResult> GetNumberThreads;
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, uint, uint, uint*, uint*, HResult> GetThreadIdsByIndex;
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, uint, uint, uint, byte*, HResult> GetThreadContextBySystemId;
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, out uint, HResult> GetCurrentProcessSystemId;
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, out uint, HResult> GetCurrentThreadSystemId;
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, uint, HResult> SetCurrentThreadSystemId;
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, uint, out ulong, HResult> GetThreadTeb;
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, uint, uint, byte*, HResult> VirtualUnwind;
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, byte*, uint, out uint, HResult> GetSymbolPath;
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, int, ulong, byte*, int, out uint, out ulong, HResult> GetSymbolByOffset;
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, int, byte*, out ulong, HResult> GetOffsetBySymbol;
+        [StructLayout(LayoutKind.Sequential)]
+        private readonly unsafe struct IDebuggerServicesVTable
+        {
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, out DebuggerServices.OperatingSystem, HResult> GetOperatingSystem;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, out DEBUG_CLASS, out DEBUG_CLASS_QUALIFIER, HResult> GetDebuggeeType;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, out IMAGE_FILE_MACHINE, HResult> GetExecutingProcessorType;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, byte*, byte*, IntPtr*, int, HResult> AddCommand;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, DEBUG_OUTPUT, byte*, void> OutputString;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, ulong, byte*, uint, out int, HResult> ReadVirtual;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, ulong, byte*, uint, out int, HResult> WriteVirtual;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, out uint, out uint, HResult> GetNumberModules;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, uint, ulong, byte*, uint, out uint, byte*, uint, uint*, byte*, uint, uint*, HResult> GetModuleNames;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, uint, out ulong, out ulong, out uint, out uint, HResult> GetModuleInfo;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, uint, ulong, byte*, byte*, uint, uint*, HResult> GetModuleVersionInformation;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, out uint, HResult> GetNumberThreads;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, uint, uint, uint*, uint*, HResult> GetThreadIdsByIndex;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, uint, uint, uint, byte*, HResult> GetThreadContextBySystemId;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, out uint, HResult> GetCurrentProcessSystemId;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, out uint, HResult> GetCurrentThreadSystemId;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, uint, HResult> SetCurrentThreadSystemId;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, uint, out ulong, HResult> GetThreadTeb;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, uint, uint, byte*, HResult> VirtualUnwind;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, byte*, uint, out uint, HResult> GetSymbolPath;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, int, ulong, byte*, int, out uint, out ulong, HResult> GetSymbolByOffset;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, int, byte*, out ulong, HResult> GetOffsetBySymbol;
+        }
     }
 }
index 0f4af6ed677a97d4e22b6aaaa930f842d1850346..70aa7598ef146541ab85fe6a795246566d87dc9c 100644 (file)
@@ -13,7 +13,7 @@ namespace SOS
 {
     internal unsafe class RemoteMemoryService : CallableCOMWrapper, IRemoteMemoryService
     {
-        internal static Guid IID_IRemoteMemoryService = new Guid("CD6A0F22-8BCF-4297-9366-F440C2D1C781");
+        private static Guid IID_IRemoteMemoryService = new Guid("CD6A0F22-8BCF-4297-9366-F440C2D1C781");
 
         private ref readonly IRemoteMemoryServiceVTable VTable => ref Unsafe.AsRef<IRemoteMemoryServiceVTable>(_vtable);
 
@@ -31,12 +31,12 @@ namespace SOS
         {
             return VTable.FreeVirtual(Self, address, size, typeFlags) == HResult.S_OK;
         }
-    }
 
-    [StructLayout(LayoutKind.Sequential)]
-    internal readonly unsafe struct IRemoteMemoryServiceVTable
-    {
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, ulong, uint, uint, uint, out ulong, HResult> AllocVirtual;
-        public readonly delegate* unmanaged[Stdcall]<IntPtr, ulong, uint, uint, HResult> FreeVirtual;
+        [StructLayout(LayoutKind.Sequential)]
+        private readonly unsafe struct IRemoteMemoryServiceVTable
+        {
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, ulong, uint, uint, uint, out ulong, HResult> AllocVirtual;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, ulong, uint, uint, HResult> FreeVirtual;
+        }
     }
 }
index 9139b399c3ff72fdde186e0293c5a71898136358..0954470bc52501f44fc380420b9101d03644ba25 100644 (file)
@@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
 
 namespace SOS.Hosting
 {
-    internal sealed unsafe class CorDebugDataTargetWrapper : COMCallableIUnknown
+    public sealed unsafe class CorDebugDataTargetWrapper : COMCallableIUnknown
     {
         private static readonly Guid IID_ICorDebugDataTarget = new Guid("FE06DC28-49FB-4636-A4A3-E80DB4AE116C");
         private static readonly Guid IID_ICorDebugDataTarget4 = new Guid("E799DC06-E099-4713-BDD9-906D3CC02CF2");
@@ -26,7 +26,7 @@ namespace SOS.Hosting
 
         public IntPtr ICorDebugDataTarget { get; }
 
-        internal CorDebugDataTargetWrapper(IServiceProvider services)
+        public CorDebugDataTargetWrapper(IServiceProvider services)
         {
             Debug.Assert(services != null);
             _target = services.GetService<ITarget>();
index 62928a90df1f259216072a95abc29457b400047c..c10e74136c58bbf65aaff194ba76bdedfc61f29c 100644 (file)
@@ -14,7 +14,7 @@ using System.Text;
 
 namespace SOS.Hosting
 {
-    internal sealed unsafe class RuntimeWrapper : COMCallableIUnknown
+    public sealed unsafe class RuntimeWrapper : COMCallableIUnknown
     {
         /// <summary>
         /// The runtime OS and type. Must match IRuntime::RuntimeConfiguration in runtime.h.
@@ -28,9 +28,9 @@ namespace SOS.Hosting
             Unknown = 4
         }
 
+        public static Guid IID_IXCLRDataProcess = new Guid("5c552ab6-fc09-4cb3-8e36-22fa03c798b7");
+        public static Guid IID_ICorDebugProcess = new Guid("3d6f5f64-7538-11d3-8d5b-00104b35e7ef");
         private static readonly Guid IID_IRuntime = new Guid("A5F152B9-BA78-4512-9228-5091A4CB7E35");
-        private static Guid IID_IXCLRDataProcess = new Guid("5c552ab6-fc09-4cb3-8e36-22fa03c798b7");
-        private static Guid IID_ICorDebugProcess = new Guid("3d6f5f64-7538-11d3-8d5b-00104b35e7ef");
 
         #region DAC and DBI function delegates
 
@@ -91,7 +91,7 @@ namespace SOS.Hosting
 
         public IntPtr IRuntime { get; }
 
-        internal RuntimeWrapper(IServiceProvider services, IRuntime runtime)
+        public RuntimeWrapper(IServiceProvider services, IRuntime runtime)
         {
             Debug.Assert(services != null);
             Debug.Assert(runtime != null);
index ca58c841ab282cd6c5328ce94e0d8082422e1cbe..38cab1529a41d45fd8db1aff12e21912c507ef6b 100644 (file)
@@ -21,9 +21,6 @@
   <LogDir>$(RootBinDir)/TestResults/$(TargetConfiguration)/sos.unittests_$(Timestamp)</LogDir>
   <DumpDir>$(RootBinDir)/tmp/$(TargetConfiguration)\dumps</DumpDir>
 
-  <OSRid>linux</OSRid>
-  <OSRid Condition="$(OS) == OSX">osx</OSRid>
-
   <TestWebApp3>true</TestWebApp3>
   <TestWebApp3 Condition="'$(InternalReleaseTesting)' == 'true'">false</TestWebApp3>
 
@@ -59,7 +56,7 @@
            may not have been built with the runtime pointed by RuntimeSymbolsPath
            since we use the arcade provided SDK (in .dotnet) to build them. -->
       <SetSymbolServer>-ms</SetSymbolServer>
-      <BuildProjectRuntime>$(OSRid)-$(TargetArchitecture)</BuildProjectRuntime>
+      <BuildProjectRuntime>$(TargetRid)</BuildProjectRuntime>
     </Option>
     <!--
         Default (prebuilt)
@@ -92,7 +89,7 @@
           <RuntimeFrameworkVersion>$(RuntimeVersionLatest)</RuntimeFrameworkVersion>
           <PublishSingleFile>true</PublishSingleFile>
           <SetSymbolServer>-ms</SetSymbolServer>
-          <BuildProjectRuntime>$(OSRid)-$(TargetArchitecture)</BuildProjectRuntime>
+          <BuildProjectRuntime>$(TargetRid)</BuildProjectRuntime>
         </Option>
         <Option Condition="'$(RuntimeVersionLatest)' != ''">
           <BuildProjectFramework>$(BuildProjectFrameworkLatest)</BuildProjectFramework>
index 3a77218d0c120d2b36f57ba698c6d7feb5a3c0c7..7824b28205bf20ec57f4281df5edcf5be53efd14 100644 (file)
@@ -72,7 +72,7 @@
                may not have been built with the runtime pointed by RuntimeSymbolsPath
                since we use the arcade provided SDK (in .dotnet) to build them. -->
           <SetSymbolServer>-ms</SetSymbolServer>
-          <BuildProjectRuntime>win-$(TargetArchitecture)</BuildProjectRuntime>
+          <BuildProjectRuntime>$(TargetRid)</BuildProjectRuntime>
         </Option>
         <!--
             Default (prebuilt)
               <RuntimeFrameworkVersion>$(RuntimeVersionLatest)</RuntimeFrameworkVersion>
               <PublishSingleFile>true</PublishSingleFile>
               <SetSymbolServer>-ms</SetSymbolServer>
-              <BuildProjectRuntime>win-$(TargetArchitecture)</BuildProjectRuntime>
+              <BuildProjectRuntime>$(TargetRid)</BuildProjectRuntime>
             </Option>
             <Option Condition="'$(RuntimeVersionLatest)' != ''">
               <BuildProjectFramework>$(BuildProjectFrameworkLatest)</BuildProjectFramework>
               <RuntimeFrameworkVersion>$(RuntimeVersionLatest)</RuntimeFrameworkVersion>
               <PublishSingleFile>true</PublishSingleFile>
               <SetSymbolServer>-ms</SetSymbolServer>
-              <BuildProjectRuntime>win-$(TargetArchitecture)</BuildProjectRuntime>
+              <BuildProjectRuntime>$(TargetRid)</BuildProjectRuntime>
             </Option>
             <Option Condition="'$(AspNetCoreVersionLatest)' != ''">
               <BuildProjectFramework>$(BuildProjectFrameworkLatest)</BuildProjectFramework>
       <DebuggeeBuildProcess>cli</DebuggeeBuildProcess>
       <DebuggeeBuildRoot>$(RootBinDir)\Debuggees</DebuggeeBuildRoot>
       <BuildProjectFramework>$(DesktopFramework)</BuildProjectFramework>
-      <BuildProjectRuntime>win-$(TargetArchitecture)</BuildProjectRuntime>
+      <BuildProjectRuntime>$(TargetRid)</BuildProjectRuntime>
       <DebugType>full</DebugType>
       <RuntimeSymbolsPath>$(DesktopFrameworkPath)</RuntimeSymbolsPath>
       <SetHostRuntime>-netfx</SetHostRuntime>
index e3c6b9599346b378b471b41e666565f46e603df1..11277eddde55892e664cf799d81fff1191d379ba 100644 (file)
@@ -1,9 +1,6 @@
 <Project Sdk="Microsoft.NET.Sdk">
-
-  <Import Project="$(RepositoryEngineeringDir)\InstallRuntimes.proj" />
-
   <PropertyGroup>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <TargetFramework>net6.0</TargetFramework>
     <IsPackable>false</IsPackable>
     <NoWarn>;1591;1701</NoWarn>
     <DefineConstants>$(DefineConstants);CORE_CLR</DefineConstants>
@@ -34,7 +31,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <ProjectReference Include="..\..\Microsoft.Diagnostics.TestHelpers\Microsoft.Diagnostics.TestHelpers.csproj" />
+    <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.TestHelpers\Microsoft.Diagnostics.TestHelpers.csproj" />
   </ItemGroup>
     
   <ItemGroup>
index ccdc3779fb7faa50efc1fbc7f6cc629890d7ba5a..c6d52839b2d5f7f5ee97d058be6d3021ccdda1c6 100644 (file)
@@ -84,7 +84,7 @@
   </PropertyGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <ClCompile>
-      <AdditionalIncludeDirectories>$(SolutionDir)src\SOS\Strike\platform;$(SolutionDir)src\SOS\inc;$(SolutionDir)src\inc;$(SolutionDir)src\pal\prebuilt\inc;$(SolutionDir)src\SOS\extensions;$(SolutionDir)src\SOS\gcdump;C:\Program Files (x86)\Microsoft Visual Studio\Preview\Enterprise\DIA SDK\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>$(RepoRoot)artifacts\obj;$(RepoRoot)src\shared;$(RepoRoot)src\shared\inc;$(RepoRoot)src\shared\pal\prebuilt\inc;$(RepoRoot)src\shared\pal\inc;$(RepoRoot)src\shared\pal\inc\rt;$(SolutionDir)src\shared\gcdump;$(SolutionDir)src\SOS\Strike\platform;$(SolutionDir)src\SOS\inc;$(SolutionDir)src\SOS\extensions;C:\Program Files (x86)\Microsoft Visual Studio\Preview\Enterprise\DIA SDK\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <AdditionalOptions>%(AdditionalOptions) /d2Zi+ /Zm200 /ZH:SHA_256 /source-charset:utf-8 /homeparams</AdditionalOptions>
       <AssemblerListingLocation>Debug/</AssemblerListingLocation>
       <BufferSecurityCheck>true</BufferSecurityCheck>
       <WarningLevel>Level3</WarningLevel>
       <PreprocessorDefinitions>DEBUG;_DEBUG;_DBG;URTBLDENV_FRIENDLY=Checked;BUILDENV_CHECKED=1;_AMD64_;_WIN64;AMD64;BIT64=1;_TARGET_64BIT_=1;_TARGET_AMD64_=1;DBG_TARGET_64BIT=1;DBG_TARGET_AMD64=1;DBG_TARGET_WIN64=1;WIN32;_WIN32;WINVER=0x0602;_WIN32_WINNT=0x0602;WIN32_LEAN_AND_MEAN=1;_CRT_SECURE_NO_WARNINGS;FEATURE_COMINTEROP;FEATURE_HIJACK;_SECURE_SCL=0;_TARGET_WIN64_=1;SOS_TARGET_AMD64=1;SOS_TARGET_ARM64=1;STRIKE;USE_STL;FX_VER_INTERNALNAME_STR=SOS.dll;CMAKE_INTDIR="Debug";sos_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <ObjectFileName>$(IntDir)</ObjectFileName>
+      <BrowseInformation>true</BrowseInformation>
     </ClCompile>
     <ResourceCompile>
       <PreprocessorDefinitions>WIN32;_DEBUG;DEBUG;_DBG;URTBLDENV_FRIENDLY=Checked;BUILDENV_CHECKED=1;_AMD64_;_WIN64;AMD64;BIT64=1;_TARGET_64BIT_=1;_TARGET_AMD64_=1;DBG_TARGET_64BIT=1;DBG_TARGET_AMD64=1;DBG_TARGET_WIN64=1;_WIN32;WINVER=0x0602;_WIN32_WINNT=0x0602;WIN32_LEAN_AND_MEAN=1;_CRT_SECURE_NO_WARNINGS;;FEATURE_COMINTEROP;FEATURE_HIJACK;_SECURE_SCL=0;_TARGET_WIN64_=1;SOS_TARGET_AMD64=1;SOS_TARGET_ARM64=1;STRIKE;USE_STL;FX_VER_INTERNALNAME_STR=SOS.dll;CMAKE_INTDIR=\"Debug\";sos_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     <ProjectReference>
       <LinkLibraryDependencies>false</LinkLibraryDependencies>
     </ProjectReference>
+    <Bscmake>
+      <PreserveSbr>true</PreserveSbr>
+    </Bscmake>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Checked|x64'">
     <ClCompile>
   <ImportGroup Label="ExtensionTargets">
     <Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
   </ImportGroup>
-</Project>
+</Project>
\ No newline at end of file
index 77431d9b0311de644280c770456f09b11de29795..df9fb2c70ded83f837263546e93aa6f037f57ed9 100644 (file)
@@ -54,18 +54,18 @@ typedef HMODULE (STDAPICALLTYPE  *LoadLibraryWFnPtr)(LPCWSTR lpLibFileName);
 // Current runtime instance
 IRuntime* g_pRuntime = nullptr;
 
-#if !defined(__APPLE__)
+extern "C" bool TryGetSymbolWithCallback(
+    bool (*readMemory)(void* address, void* buffer, size_t size),
+    ULONG64 baseAddress,
+    const char* symbolName,
+    ULONG64* symbolAddress);
 
-extern "C" bool TryGetSymbol(uint64_t baseAddress, const char* symbolName, uint64_t* symbolAddress);
-
-bool ElfReaderReadMemory(void* address, void* buffer, size_t size)
+bool ReaderReadMemory(void* address, void* buffer, size_t size)
 {
     ULONG read = 0;
     return SUCCEEDED(g_ExtData->ReadVirtual((ULONG64)address, buffer, (ULONG)size, &read));
 }
 
-#endif // !defined(__APPLE__)
-
 /**********************************************************************\
  * Search all the modules in the process for the single-file host
 \**********************************************************************/
@@ -96,15 +96,14 @@ static HRESULT GetSingleFileInfo(ITarget* target, PULONG pModuleIndex, PULONG64
             return hr;
         }
         ULONG64 symbolAddress;
-#if !defined(__APPLE__)
-        if (target->GetOperatingSystem() == ITarget::OperatingSystem::Linux)
+        if (target->GetOperatingSystem() == ITarget::OperatingSystem::Linux ||
+            target->GetOperatingSystem() == ITarget::OperatingSystem::OSX)
         {
-            if (!TryGetSymbol(baseAddress, symbolName, &symbolAddress)) {
+            if (!::TryGetSymbolWithCallback(ReaderReadMemory, baseAddress, symbolName, &symbolAddress)) {
                 continue;
             }
         }
         else 
-#endif // !defined(__APPLE__)
         {
             hr = debuggerServices->GetOffsetBySymbol(index, symbolName, &symbolAddress);
             if (FAILED(hr)) {
@@ -120,6 +119,9 @@ static HRESULT GetSingleFileInfo(ITarget* target, PULONG pModuleIndex, PULONG64
         if (strcmp(((RuntimeInfo*)buffer.GetPtr())->Signature, "DotNetRuntimeInfo") != 0) {
             break;
         }
+        if (((RuntimeInfo*)buffer.GetPtr())->Version <= 0) {
+            break;
+        }
         *pModuleIndex = index;
         *pModuleAddress = baseAddress;
         *ppRuntimeInfo = (RuntimeInfo*)buffer.Detach();
index 54a8de308af80c81f27c0bfe827c230b4dc22299..0a70e39971837e6a462918dd72e8580b1611428f 100644 (file)
@@ -21,7 +21,6 @@
 #include <psapi.h>
 #include <cordebug.h>
 #include <xcordebug.h>
-#include <metahost.h>
 #include <mscoree.h>
 #include <tchar.h>
 #include "gcinfo.h"
index e8a5d36fb64061ed991d6bccd65520b514e4a36c..2307c540c518bf72fc9c993612eb45dcc7a301ad 100644 (file)
@@ -16,7 +16,6 @@ inline void RestoreSOToleranceState() {}
 #include <corsym.h>
 #include <clrdata.h>
 #include <palclr.h>
-#include <metahost.h>
 #include <new>
 #include <functional>
 
index 560de5f1348030d6748acb81a236c2895b5b02ff..117d973477d3820d4d392f961233fffa5af1bc32 100644 (file)
@@ -76,7 +76,7 @@
   </PropertyGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <ClCompile>
-      <AdditionalIncludeDirectories>$(SolutionDir)src\SOS\inc;$(SolutionDir)src\SOS\extensions;$(SolutionDir)src\SOS\gcdump;$(SolutionDir)src\inc;$(SolutionDir)src\pal\prebuilt\inc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>$(SolutionDir)src\SOS\inc;$(SolutionDir)src\SOS\extensions;$(SolutionDir)src\shared\gcdump;$(SolutionDir)src\shared\inc;$(SolutionDir)src\shared\pal\prebuilt\inc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <AdditionalOptions>%(AdditionalOptions) /d2Zi+ /Zm200 /ZH:SHA_256 /source-charset:utf-8 /homeparams</AdditionalOptions>
       <AssemblerListingLocation>Debug/</AssemblerListingLocation>
       <BufferSecurityCheck>true</BufferSecurityCheck>
       <WarningLevel>Level3</WarningLevel>
       <PreprocessorDefinitions>DEBUG;_DEBUG;_DBG;URTBLDENV_FRIENDLY=Checked;BUILDENV_CHECKED=1;_AMD64_;_WIN64;AMD64;BIT64=1;_TARGET_64BIT_=1;_TARGET_AMD64_=1;DBG_TARGET_64BIT=1;DBG_TARGET_AMD64=1;DBG_TARGET_WIN64=1;WIN32;_WIN32;WINVER=0x0602;_WIN32_WINNT=0x0602;WIN32_LEAN_AND_MEAN=1;_CRT_SECURE_NO_WARNINGS;FEATURE_COMINTEROP;FEATURE_HIJACK;_SECURE_SCL=0;CMAKE_INTDIR="Debug";%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <ObjectFileName>$(IntDir)</ObjectFileName>
+      <BrowseInformation>true</BrowseInformation>
     </ClCompile>
     <ResourceCompile>
       <PreprocessorDefinitions>WIN32;_DEBUG;DEBUG;_DBG;URTBLDENV_FRIENDLY=Checked;BUILDENV_CHECKED=1;_AMD64_;_WIN64;AMD64;BIT64=1;_TARGET_64BIT_=1;_TARGET_AMD64_=1;DBG_TARGET_64BIT=1;DBG_TARGET_AMD64=1;DBG_TARGET_WIN64=1;_WIN32;WINVER=0x0602;_WIN32_WINNT=0x0602;WIN32_LEAN_AND_MEAN=1;_CRT_SECURE_NO_WARNINGS;;FEATURE_COMINTEROP;FEATURE_HIJACK;_SECURE_SCL=0;HOST_IS_WINDOWS_OS;CMAKE_INTDIR=\"Debug\";%(PreprocessorDefinitions)</PreprocessorDefinitions>
     <Lib>
       <AdditionalOptions>%(AdditionalOptions) /machine:x64 /IGNORE:4221</AdditionalOptions>
     </Lib>
+    <Bscmake>
+      <PreserveSbr>true</PreserveSbr>
+    </Bscmake>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Checked|x64'">
     <ClCompile>
   <ImportGroup Label="ExtensionTargets">
     <Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
   </ImportGroup>
-</Project>
+</Project>
\ No newline at end of file
index 555953b61063fdb106177fe7d6a71b68183899f2..40c35b58fae40912380dcc30433950a8bf9e8f52 100644 (file)
   </ItemGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <ClCompile>
-      <AdditionalIncludeDirectories>$(SolutionDir)src\SOS\inc;$(SolutionDir)src\SOS\extensions;$(SolutionDir)src\inc;$(SolutionDir)src\pal\inc;$(SolutionDir)src\pal\inc\rt;$(SolutionDir)src\pal\inc\rt\cpp;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>$(SolutionDir)src\SOS\inc;$(SolutionDir)src\SOS\extensions;$(SolutionDir)src\shared\inc;$(SolutionDir)src\shared\pal\inc;$(SolutionDir)src\shared\pal\inc\rt;$(SolutionDir)src\shared\pal\inc\rt\cpp;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>_AMD64_;_WIN64;AMD64;_TARGET_64BIT_=1;_TARGET_AMD64_=1;DBG_TARGET_64BIT=1;DBG_TARGET_AMD64=1;DBG_TARGET_WIN64=1;DBG_TARGET_AMD64_UNIX;BIT64=1;PAL_STDCPP_COMPAT;_SECURE_SCL=0;LINUX64;PLATFORM_UNIX=1;FEATURE_PAL;FEATURE_PAL_ANSI;UNIX_AMD64_ABI;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <BrowseInformation>true</BrowseInformation>
     </ClCompile>
+    <Bscmake>
+      <PreserveSbr>true</PreserveSbr>
+    </Bscmake>
   </ItemDefinitionGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets" />
index a01713bf5203cacb7ad63a17edc4bf7087670b89..e97ee53192617e6ebe64b01d4335bdc044e4053b 100644 (file)
@@ -796,13 +796,17 @@ LLDBServices::ReadVirtual(
             {
                 lldb::SBSection section = module.GetSectionAtIndex(j);
                 lldb::addr_t loadAddr = section.GetLoadAddress(target);
-                lldb::addr_t byteSize = section.GetByteSize();
-                if ((loadAddr != LLDB_INVALID_ADDRESS) && (offset >= loadAddr) && (offset < loadAddr + byteSize))
+                lldb::addr_t endAddr = loadAddr + section.GetByteSize();
+                ULONG64 endOffset = offset + bufferSize;
+                if ((loadAddr != LLDB_INVALID_ADDRESS) && (offset >= loadAddr) && (endOffset < endAddr))
                 {
                     lldb::SBData sectionData = section.GetSectionData(offset - loadAddr, bufferSize);
-                    read = sectionData.ReadRawData(error, 0, buffer, bufferSize);
-                    found = true;
-                    break;
+                    if (sectionData.IsValid())
+                    {
+                        read = sectionData.ReadRawData(error, 0, buffer, bufferSize);
+                        found = true;
+                        break;
+                    }
                 }
             }
         }
index 1625bb2819d333fa297b281f206cc2d66aff9d59..62e404fcc5b379f6422d50e2d717e7baf1f00b4e 100644 (file)
@@ -45,8 +45,8 @@ if(CLR_CMAKE_HOST_UNIX)
 endif(CLR_CMAKE_HOST_UNIX)
 
 set(DBGSHIM_LIBRARIES
-    corguids
     dbgutil
+    corguids
     utilcodestaticnohost
 )
 
@@ -66,8 +66,8 @@ if(CLR_CMAKE_HOST_WIN32)
     )
 else()
     list(APPEND DBGSHIM_LIBRARIES
-        coreclrpal
         palrt
+        coreclrpal
     )
 endif(CLR_CMAKE_HOST_WIN32)
 
index c82625487cfe85359f88ab76c4ef894cb2177c13..0c418aecb178cb9a3e14b77af92361e3e1131e82 100644 (file)
@@ -14,7 +14,7 @@
 #include <tlhelp32.h>
 #include <cor.h>
 #include <sstring.h>
-#ifndef TARGET_UNIX
+#ifdef TARGET_WINDOWS
 #include <securityutil.h>
 #endif
 
 #include <dbgenginemetrics.h>
 #include <arrayholder.h>
 
-#ifndef TARGET_UNIX
+#ifdef TARGET_WINDOWS
 #define PSAPI_VERSION 2
 #include <psapi.h>
 #endif
 
 #include "dbgshim.h"
+#include "debugshim.h"
 
 /*
 
@@ -69,112 +70,12 @@ Notes:
     } \
     CONTRACTL_END;
 
-//-----------------------------------------------------------------------------
-// Public API.
-//
-// CreateProcessForLaunch - a stripped down version of the Windows CreateProcess
-// that can be supported cross-platform.
-//
-//-----------------------------------------------------------------------------
-DLLEXPORT
-HRESULT
-CreateProcessForLaunch(
-    _In_ LPWSTR lpCommandLine,
-    _In_ BOOL bSuspendProcess,
-    _In_ LPVOID lpEnvironment,
-    _In_ LPCWSTR lpCurrentDirectory,
-    _Out_ PDWORD pProcessId,
-    _Out_ HANDLE *pResumeHandle)
-{
-    PUBLIC_CONTRACT;
-
-    PROCESS_INFORMATION processInfo;
-    STARTUPINFOW startupInfo;
-    DWORD dwCreationFlags = 0;
-
-    ZeroMemory(&processInfo, sizeof(processInfo));
-    ZeroMemory(&startupInfo, sizeof(startupInfo));
-
-    startupInfo.cb = sizeof(startupInfo);
-
-    if (bSuspendProcess)
-    {
-        dwCreationFlags = CREATE_SUSPENDED;
-    }
-
-    BOOL result = CreateProcessW(
-        NULL,
-        lpCommandLine,
-        NULL,
-        NULL,
-        FALSE,
-        dwCreationFlags,
-        lpEnvironment,
-        lpCurrentDirectory,
-        &startupInfo,
-        &processInfo);
-
-    if (!result) {
-        *pProcessId = 0;
-        *pResumeHandle = NULL;
-        return HRESULT_FROM_WIN32(GetLastError());
-    }
-
-    if (processInfo.hProcess != NULL)
-    {
-        CloseHandle(processInfo.hProcess);
-    }
-
-    *pProcessId = processInfo.dwProcessId;
-    *pResumeHandle = processInfo.hThread;
-
-    return S_OK;
-}
-
-//-----------------------------------------------------------------------------
-// Public API.
-//
-// ResumeProcess - to be used with the CreateProcessForLaunch resume handle
-//
-//-----------------------------------------------------------------------------
-DLLEXPORT
-HRESULT
-ResumeProcess(
-    _In_ HANDLE hResumeHandle)
-{
-    PUBLIC_CONTRACT;
-    if (ResumeThread(hResumeHandle) == (DWORD)-1)
-    {
-        return HRESULT_FROM_WIN32(GetLastError());
-    }
-    return S_OK;
-}
-
-//-----------------------------------------------------------------------------
-// Public API.
-//
-// CloseResumeHandle - to be used with the CreateProcessForLaunch resume handle
-//
-//-----------------------------------------------------------------------------
-DLLEXPORT
-HRESULT
-CloseResumeHandle(
-    _In_ HANDLE hResumeHandle)
-{
-    PUBLIC_CONTRACT;
-    if (!CloseHandle(hResumeHandle))
-    {
-        return HRESULT_FROM_WIN32(GetLastError());
-    }
-    return S_OK;
-}
-
 #ifdef TARGET_UNIX
 
 static
-void
+bool
 RuntimeStartupHandler(
-    char *pszModulePath,
+    const char *pszModulePath,
     HMODULE hModule,
     PVOID parameter);
 
@@ -185,14 +86,46 @@ DWORD
 StartupHelperThread(
     LPVOID p);
 
-static
+#endif // TARGET_UNIX
+
+struct ClrRuntimeInfo
+{
+    HMODULE ModuleHandle;
+    HANDLE ContinueStartupEvent;
+    CLR_ENGINE_METRICS EngineMetrics;
+    ClrInfo ClrInfo;
+
+    ClrRuntimeInfo()
+    {
+        ModuleHandle = NULL;
+        ContinueStartupEvent = INVALID_HANDLE_VALUE;
+
+        EngineMetrics.cbSize = sizeof(EngineMetrics);
+        EngineMetrics.dwDbiVersion = CorDebugLatestVersion;
+        EngineMetrics.phContinueStartupEvent = NULL;
+    }
+};
+
 HRESULT
-GetContinueStartupEvent(
+GetRuntime(
     DWORD debuggeePID,
-    LPCWSTR szTelestoFullPath,
-    _Out_ HANDLE *phContinueStartupEvent);
+    ClrRuntimeInfo& clrRuntimeInfo);
 
-#endif // TARGET_UNIX
+HRESULT
+GetTargetCLRMetrics(
+    LPCWSTR wszModulePath,
+    CLR_ENGINE_METRICS *pEngineMetricsOut,
+    ClrInfo* pClrInfoOut = NULL,
+    DWORD *pdwRVAContinueStartupEvent = NULL);
+
+void
+AppendDbiDllName(
+    SString & szFullDbiPath);
+
+bool
+CheckDbiAndRuntimeVersion(
+    SString & szFullDbiPath,
+    SString & szFullCoreClrPath);
 
 // Functions that we'll look for in the loaded Mscordbi module.
 typedef HRESULT (STDAPICALLTYPE *FPCoreCLRCreateCordbObject)(
@@ -208,44 +141,73 @@ typedef HRESULT (STDAPICALLTYPE *FPCoreCLRCreateCordbObjectEx)(
     HMODULE hmodTargetCLR,
     IUnknown **ppCordb);
 
-HRESULT CreateCoreDbgWithSandboxSupport(
-    HMODULE hCLRModule, HMODULE hDBIModule, DWORD processId, LPCWSTR lpApplicationGroupId, int iDebuggerVersion, IUnknown **ppCordb)
-{
-    FPCoreCLRCreateCordbObjectEx fpCreate =
-        (FPCoreCLRCreateCordbObjectEx)GetProcAddress(hDBIModule, "CoreCLRCreateCordbObjectEx");
-    if (fpCreate == NULL)
-    {
-        return CORDBG_E_INCOMPATIBLE_PROTOCOL;
-    }
-
-    return fpCreate(iDebuggerVersion, processId, lpApplicationGroupId, hCLRModule, ppCordb);
-}
-
-HRESULT CreateCoreDbgWithoutSandboxSupport(
-    HMODULE hCLRModule, HMODULE hDBIModule, DWORD processId, int iDebuggerVersion, IUnknown **ppCordb)
-{
-    FPCoreCLRCreateCordbObject fpCreate =
-        (FPCoreCLRCreateCordbObject)GetProcAddress(hDBIModule, "CoreCLRCreateCordbObject");
-    if (fpCreate == NULL)
-    {
-        return CORDBG_E_INCOMPATIBLE_PROTOCOL;
-    }
-
-    return fpCreate(iDebuggerVersion, processId, hCLRModule, ppCordb);
-}
+typedef HRESULT (STDAPICALLTYPE *FPCoreCLRCreateCordbObject3)(
+    int iDebuggerVersion,
+    DWORD pid,
+    LPCWSTR lpApplicationGroupId,
+    LPCWSTR dacModulePath,
+    HMODULE hmodTargetCLR,
+    IUnknown **ppCordb);
 
 HRESULT CreateCoreDbg(
-    HMODULE hCLRModule, HMODULE hDBIModule, DWORD processId, LPCWSTR lpApplicationGroupId, int iDebuggerVersion, IUnknown **ppCordb)
+    HMODULE hCLRModule,
+    DWORD processId,
+    SString& dbiModulePath,
+    SString& dacModulePath,
+    LPCWSTR lpApplicationGroupId,
+    int iDebuggerVersion,
+    IUnknown **ppCordb)
 {
+    HMODULE hDbi = NULL;
     HRESULT hr = S_OK;
 
-    if (lpApplicationGroupId != NULL)
+    hDbi = LoadLibraryW(dbiModulePath);
+    if (hDbi != NULL)
     {
-        hr = CreateCoreDbgWithSandboxSupport(hCLRModule, hDBIModule, processId, lpApplicationGroupId, iDebuggerVersion, ppCordb);
+        FPCoreCLRCreateCordbObject3 fpCreate3 = (FPCoreCLRCreateCordbObject3)GetProcAddress(hDbi, "CoreCLRCreateCordbObject3");
+        if (fpCreate3 != NULL)
+        {
+            hr = fpCreate3(iDebuggerVersion, processId, lpApplicationGroupId, dacModulePath.IsEmpty() ? NULL : (LPCWSTR)dacModulePath, hCLRModule, ppCordb);
+        }
+        else
+        {
+            if (lpApplicationGroupId != NULL)
+            {
+                FPCoreCLRCreateCordbObjectEx fpCreateEx = (FPCoreCLRCreateCordbObjectEx)GetProcAddress(hDbi, "CoreCLRCreateCordbObjectEx");
+                if (fpCreateEx != NULL)
+                {
+                    hr = fpCreateEx(iDebuggerVersion, processId, lpApplicationGroupId, hCLRModule, ppCordb);
+                }
+                else
+                {
+                    hr = CORDBG_E_INCOMPATIBLE_PROTOCOL;
+                }
+            }
+            else
+            {
+                FPCoreCLRCreateCordbObject fpCreate = (FPCoreCLRCreateCordbObject)GetProcAddress(hDbi, "CoreCLRCreateCordbObject");
+                if (fpCreate != NULL)
+                {
+                    hr = fpCreate(iDebuggerVersion, processId, hCLRModule, ppCordb);
+                }
+                else
+                {
+                    hr = CORDBG_E_INCOMPATIBLE_PROTOCOL;
+                }
+            }
+        }
     }
     else
     {
-        hr = CreateCoreDbgWithoutSandboxSupport(hCLRModule, hDBIModule, processId, iDebuggerVersion, ppCordb);
+        hr = CORDBG_E_DEBUG_COMPONENT_MISSING;
+    }
+
+    if (FAILED(hr))
+    {
+        if (hDbi != NULL)
+        {
+            FreeLibrary(hDbi);
+        }
     }
 
     return hr;
@@ -258,6 +220,7 @@ class RuntimeStartupHelper
 {
     LONG m_ref;
     DWORD m_processId;
+    ICLRDebuggingLibraryProvider3* m_pLibraryProvider;
     PSTARTUP_CALLBACK m_callback;
     PVOID m_parameter;
 #ifdef TARGET_UNIX
@@ -271,9 +234,11 @@ class RuntimeStartupHelper
 #endif // TARGET_UNIX
 
 public:
-    RuntimeStartupHelper(DWORD dwProcessId, PSTARTUP_CALLBACK pfnCallback, PVOID parameter) :
+    
+    RuntimeStartupHelper(DWORD dwProcessId, ICLRDebuggingLibraryProvider3* pLibraryProvider, PSTARTUP_CALLBACK pfnCallback, PVOID parameter) :
         m_ref(1),
         m_processId(dwProcessId),
+        m_pLibraryProvider(pLibraryProvider),
         m_callback(pfnCallback),
         m_parameter(parameter),
 #ifdef TARGET_UNIX
@@ -286,10 +251,18 @@ public:
         m_threadHandle(NULL)
 #endif // TARGET_UNIX
     {
+        if (pLibraryProvider != NULL)
+        {
+            pLibraryProvider->AddRef();
+        }
     }
 
     ~RuntimeStartupHelper()
     {
+        if (m_pLibraryProvider != NULL)
+        {
+            m_pLibraryProvider->Release();
+        }
 #ifdef TARGET_UNIX
         if (m_applicationGroupId != NULL)
         {
@@ -351,11 +324,11 @@ public:
         PAL_UnregisterForRuntimeStartup(m_unregisterToken);
     }
 
-    void InvokeStartupCallback(char *pszModulePath, HMODULE hModule)
+    bool InvokeStartupCallback(const char *pszModulePath, HMODULE hModule)
     {
         IUnknown *pCordb = NULL;
-        HMODULE hMod = NULL;
         HRESULT hr = S_OK;
+        ClrInfo clrInfo;
 
         // If either of these are NULL, there was an error from the PAL
         // callback. GetLastError returns the error code from the PAL.
@@ -367,27 +340,40 @@ public:
 
         PAL_CPP_TRY
         {
-            char dbiPath[MAX_LONGPATH];
+            clrInfo.RuntimeModulePath.SetASCII(pszModulePath);
 
-            char *pszLast = strrchr(pszModulePath, DIRECTORY_SEPARATOR_CHAR_A);
-            if (pszLast == NULL)
-            {
-                _ASSERT(!"InvokeStartupCallback: can find separator in coreclr path\n");
-                hr = E_INVALIDARG;
-                goto exit;
+            // Get the DBI/DAC index info for regular and single-file apps
+            hr = GetTargetCLRMetrics(clrInfo.RuntimeModulePath, NULL, &clrInfo, NULL);
+            if (FAILED(hr))
+            { 
+                return false;
             }
 
-            strncpy_s(dbiPath, ARRAY_SIZE(dbiPath), pszModulePath, pszLast - pszModulePath);
-            strcat_s(dbiPath, ARRAY_SIZE(dbiPath), DIRECTORY_SEPARATOR_STR_A MAKEDLLNAME_A("mscordbi"));
-
-            hMod = LoadLibraryA(dbiPath);
-            if (hMod == NULL)
+            SString dbiModulePath;
+            SString dacModulePath;
+            if (m_pLibraryProvider != NULL)
             {
-                hr = CORDBG_E_DEBUG_COMPONENT_MISSING;
-                goto exit;
+                hr = CLRDebuggingImpl::ProvideLibraries(clrInfo, m_pLibraryProvider, dbiModulePath, dacModulePath);
+                if (FAILED(hr))
+                {
+                    goto exit;
+                }
+            }
+            else
+            {
+                // Fallback to loading DBI side-by-side the runtime module
+                char *pszLast = strrchr(pszModulePath, DIRECTORY_SEPARATOR_CHAR_A);
+                if (pszLast == NULL)
+                {
+                    _ASSERT(!"InvokeStartupCallback: can find separator in coreclr path\n");
+                    hr = E_INVALIDARG;
+                    goto exit;
+                }
+                dbiModulePath.SetASCII(pszModulePath, pszLast - pszModulePath);
+                AppendDbiDllName(dbiModulePath);
             }
 
-            HRESULT hr = CreateCoreDbg(hModule, hMod, m_processId, m_applicationGroupId, CorDebugVersion_2_0, &pCordb);
+            hr = CreateCoreDbg(hModule, m_processId, dbiModulePath, dacModulePath, m_applicationGroupId, CorDebugVersion_2_0, &pCordb);
             _ASSERTE((pCordb == NULL) == FAILED(hr));
             if (FAILED(hr))
             {
@@ -408,14 +394,11 @@ public:
         {
             _ASSERTE(pCordb == NULL);
 
-            if (hMod != NULL)
-            {
-                FreeLibrary(hMod);
-            }
-
             // Invoke the callback on error
             m_callback(NULL, m_parameter, hr);
         }
+
+        return true;
     }
 
 #else // TARGET_UNIX
@@ -450,51 +433,32 @@ public:
         return hr;
     }
 
-    bool AreAllHandlesValid(HANDLE *handleArray, DWORD arrayLength)
-    {
-        for (int i = 0; i < (int)arrayLength; i++)
-        {
-            HANDLE h = handleArray[i];
-            if (h == INVALID_HANDLE_VALUE)
-            {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    HRESULT InternalEnumerateCLRs(HANDLE** ppHandleArray, _In_reads_(*pdwArrayLength) LPWSTR** ppStringArray, DWORD* pdwArrayLength)
+    HRESULT InternalGetRuntime(ClrRuntimeInfo& clrRuntimeInfo)
     {
         int numTries = 0;
         HRESULT hr;
 
         while (numTries < 25)
         {
-            hr = EnumerateCLRs(m_processId, ppHandleArray, ppStringArray, pdwArrayLength);
+            hr = GetRuntime(m_processId, clrRuntimeInfo);
 
             // EnumerateCLRs uses the OS API CreateToolhelp32Snapshot which can return ERROR_BAD_LENGTH or
             // ERROR_PARTIAL_COPY. If we get either of those, we try wait 1/10th of a second try again (that
             // is the recommendation of the OS API owners).
             if ((hr != HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY)) && (hr != HRESULT_FROM_WIN32(ERROR_BAD_LENGTH)))
             {
-                // Just return any other error or if no handles were found (which means the coreclr module wasn't found yet).
-                if (FAILED(hr) || *ppHandleArray == NULL || *pdwArrayLength <= 0)
+                // Just return any other error or if no runtimes were found (which means the coreclr module wasn't found yet).
+                if (FAILED(hr) || hr == S_FALSE)
                 {
                     return hr;
                 }
-                // If EnumerateCLRs succeeded but any of the handles are INVALID_HANDLE_VALUE, then sleep and retry
-                // also. This fixes a race condition where dbgshim catches the coreclr module just being loaded but
-                // before g_hContinueStartupEvent has been initialized.
-                if (AreAllHandlesValid(*ppHandleArray, *pdwArrayLength))
+                // If GetRuntime succeeded but the handle is INVALID_HANDLE_VALUE, then sleep and retry also. This fixes a 
+                // race condition where dbgshim catches the coreclr module just being loaded but before g_hContinueStartupEvent
+                // has been initialized.
+                if (clrRuntimeInfo.ContinueStartupEvent != INVALID_HANDLE_VALUE)
                 {
                     return hr;
                 }
-                // Clean up memory allocated in EnumerateCLRs since this path it succeeded
-                CloseCLREnumeration(*ppHandleArray, *ppStringArray, *pdwArrayLength);
-
-                *ppHandleArray = NULL;
-                *ppStringArray = NULL;
-                *pdwArrayLength = 0;
             }
 
             // Sleep and retry enumerating the runtimes
@@ -513,35 +477,19 @@ public:
         return hr;
     }
 
-    void WakeRuntimes(HANDLE *handleArray, DWORD arrayLength)
-    {
-        if (handleArray != NULL)
-        {
-            for (int i = 0; i < (int)arrayLength; i++)
-            {
-                HANDLE h = handleArray[i];
-                if (h != NULL && h != INVALID_HANDLE_VALUE)
-                {
-                    SetEvent(h);
-                }
-            }
-        }
-    }
-
     void Unregister()
     {
         m_canceled = true;
 
-        HANDLE *handleArray = NULL;
-        LPWSTR *stringArray = NULL;
-        DWORD arrayLength = 0;
-
-        // Wake up runtime(s)
-        HRESULT hr = EnumerateCLRs(m_processId, &handleArray, &stringArray, &arrayLength);
+        // Wake up runtime
+        ClrRuntimeInfo clrRuntimeInfo;
+        HRESULT hr = GetRuntime(m_processId, clrRuntimeInfo);
         if (SUCCEEDED(hr))
         {
-            WakeRuntimes(handleArray, arrayLength);
-            CloseCLREnumeration(handleArray, stringArray, arrayLength);
+            if (clrRuntimeInfo.ContinueStartupEvent != NULL && clrRuntimeInfo.ContinueStartupEvent != INVALID_HANDLE_VALUE)
+            {
+                SetEvent(clrRuntimeInfo.ContinueStartupEvent);
+            }
         }
 
         // Wake up worker thread
@@ -557,45 +505,71 @@ public:
 
     HRESULT InvokeStartupCallback(bool *pCoreClrExists)
     {
-        HANDLE *handleArray = NULL;
-        LPWSTR *stringArray = NULL;
-        DWORD arrayLength = 0;
+        ClrRuntimeInfo clrRuntimeInfo;
         HRESULT hr = S_OK;
 
         PAL_CPP_TRY
         {
             IUnknown *pCordb = NULL;
-            WCHAR verStr[MAX_LONGPATH];
-            DWORD verLen;
 
             *pCoreClrExists = FALSE;
 
-            hr = InternalEnumerateCLRs(&handleArray, &stringArray, &arrayLength);
+            hr = InternalGetRuntime(clrRuntimeInfo);
             if (FAILED(hr))
             {
                 goto exit;
             }
 
-            for (int i = 0; i < (int)arrayLength; i++)
+            // S_FALSE means there are no runtimes and no falures
+            if (hr == S_OK)
             {
                 *pCoreClrExists = TRUE;
 
-                hr = CreateVersionStringFromModule(m_processId, stringArray[i], verStr, ARRAY_SIZE(verStr), &verLen);
-                if (FAILED(hr))
+                SString dbiModulePath;
+                SString dacModulePath;
+                if (m_pLibraryProvider != NULL)
                 {
-                    goto exit;
+                    hr = CLRDebuggingImpl::ProvideLibraries(clrRuntimeInfo.ClrInfo, m_pLibraryProvider, dbiModulePath, dacModulePath);
+                    if (FAILED(hr))
+                    {
+                        goto exit;
+                    }
+                }
+                else 
+                {
+                    dbiModulePath.Set(clrRuntimeInfo.ClrInfo.RuntimeModulePath);
+                    SString::Iterator iter = dbiModulePath.End();
+                    if (dbiModulePath.FindBack(iter, DIRECTORY_SEPARATOR_CHAR_W))
+                    {
+                        iter++;
+                        dbiModulePath.Truncate(iter);
+                    }
+                    else 
+                    {
+                        hr = E_FAIL;
+                        goto exit;
+                    }
+                    AppendDbiDllName(dbiModulePath);
+
+                    if (!CheckDbiAndRuntimeVersion(dbiModulePath, clrRuntimeInfo.ClrInfo.RuntimeModulePath))
+                    {
+                        hr = CORDBG_E_INCOMPATIBLE_PROTOCOL;
+                        goto exit;
+                    }
                 }
 
-                hr = CreateDebuggingInterfaceFromVersion(verStr, &pCordb);
+                hr = CreateCoreDbg(clrRuntimeInfo.ModuleHandle, m_processId, dbiModulePath, dacModulePath, NULL, clrRuntimeInfo.EngineMetrics.dwDbiVersion, &pCordb);
+                _ASSERTE((pCordb == NULL) == FAILED(hr));
                 if (FAILED(hr))
                 {
                     goto exit;
                 }
 
                 m_callback(pCordb, m_parameter, S_OK);
-
-                // Currently only the first coreclr module in a process is supported
-                break;
+            }
+            else
+            {
+                hr = S_OK;
             }
         }
         PAL_CPP_CATCH_ALL
@@ -608,10 +582,12 @@ public:
     exit:
         if (*pCoreClrExists)
         {
-            // Wake up all the runtimes
-            WakeRuntimes(handleArray, arrayLength);
+            // Wake up the runtime
+            if (clrRuntimeInfo.ContinueStartupEvent != NULL && clrRuntimeInfo.ContinueStartupEvent != INVALID_HANDLE_VALUE)
+            {
+                SetEvent(clrRuntimeInfo.ContinueStartupEvent);
+            }
         }
-        CloseCLREnumeration(handleArray, stringArray, arrayLength);
 
         return hr;
     }
@@ -621,7 +597,7 @@ public:
         bool coreclrExists = false;
 
         HRESULT hr = InvokeStartupCallback(&coreclrExists);
-        // The retry logic in InternalEnumerateCLRs failed if ERROR_TIMEOUT was returned.
+        // The retry logic in InternalGetRuntime failed if ERROR_TIMEOUT was returned.
         if (SUCCEEDED(hr) || (hr == HRESULT_FROM_WIN32(ERROR_TIMEOUT)))
         {
             if (!coreclrExists && !m_canceled)
@@ -661,11 +637,11 @@ public:
 #ifdef TARGET_UNIX
 
 static
-void
-RuntimeStartupHandler(char *pszModulePath, HMODULE hModule, PVOID parameter)
+bool
+RuntimeStartupHandler(const char *pszModulePath, HMODULE hModule, PVOID parameter)
 {
     RuntimeStartupHelper *helper = (RuntimeStartupHelper *)parameter;
-    helper->InvokeStartupCallback(pszModulePath, hModule);
+    return helper->InvokeStartupCallback(pszModulePath, hModule);
 }
 
 #else // TARGET_UNIX
@@ -682,6 +658,106 @@ StartupHelperThread(LPVOID p)
 
 #endif // TARGET_UNIX
 
+//-----------------------------------------------------------------------------
+// Public API.
+//
+// CreateProcessForLaunch - a stripped down version of the Windows CreateProcess
+// that can be supported cross-platform.
+//
+//-----------------------------------------------------------------------------
+DLLEXPORT
+HRESULT
+CreateProcessForLaunch(
+    _In_ LPWSTR lpCommandLine,
+    _In_ BOOL bSuspendProcess,
+    _In_ LPVOID lpEnvironment,
+    _In_ LPCWSTR lpCurrentDirectory,
+    _Out_ PDWORD pProcessId,
+    _Out_ HANDLE *pResumeHandle)
+{
+    PUBLIC_CONTRACT;
+
+    PROCESS_INFORMATION processInfo;
+    STARTUPINFOW startupInfo;
+    DWORD dwCreationFlags = 0;
+
+    ZeroMemory(&processInfo, sizeof(processInfo));
+    ZeroMemory(&startupInfo, sizeof(startupInfo));
+
+    startupInfo.cb = sizeof(startupInfo);
+
+    if (bSuspendProcess)
+    {
+        dwCreationFlags = CREATE_SUSPENDED;
+    }
+
+    BOOL result = CreateProcessW(
+        NULL,
+        lpCommandLine,
+        NULL,
+        NULL,
+        FALSE,
+        dwCreationFlags,
+        lpEnvironment,
+        lpCurrentDirectory,
+        &startupInfo,
+        &processInfo);
+
+    if (!result) {
+        *pProcessId = 0;
+        *pResumeHandle = NULL;
+        return HRESULT_FROM_WIN32(GetLastError());
+    }
+
+    if (processInfo.hProcess != NULL)
+    {
+        CloseHandle(processInfo.hProcess);
+    }
+
+    *pProcessId = processInfo.dwProcessId;
+    *pResumeHandle = processInfo.hThread;
+
+    return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Public API.
+//
+// ResumeProcess - to be used with the CreateProcessForLaunch resume handle
+//
+//-----------------------------------------------------------------------------
+DLLEXPORT
+HRESULT
+ResumeProcess(
+    _In_ HANDLE hResumeHandle)
+{
+    PUBLIC_CONTRACT;
+    if (ResumeThread(hResumeHandle) == (DWORD)-1)
+    {
+        return HRESULT_FROM_WIN32(GetLastError());
+    }
+    return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Public API.
+//
+// CloseResumeHandle - to be used with the CreateProcessForLaunch resume handle
+//
+//-----------------------------------------------------------------------------
+DLLEXPORT
+HRESULT
+CloseResumeHandle(
+    _In_ HANDLE hResumeHandle)
+{
+    PUBLIC_CONTRACT;
+    if (!CloseHandle(hResumeHandle))
+    {
+        return HRESULT_FROM_WIN32(GetLastError());
+    }
+    return S_OK;
+}
+
 //-----------------------------------------------------------------------------
 // Public API.
 //
@@ -702,8 +778,9 @@ RegisterForRuntimeStartup(
     _In_ PVOID parameter,
     _Out_ PVOID *ppUnregisterToken)
 {
-    return RegisterForRuntimeStartupEx(dwProcessId, NULL, pfnCallback, parameter, ppUnregisterToken);
+    return RegisterForRuntimeStartup3(dwProcessId, NULL, NULL, pfnCallback, parameter, ppUnregisterToken);
 }
+
 //-----------------------------------------------------------------------------
 // Public API.
 //
@@ -743,6 +820,51 @@ RegisterForRuntimeStartupEx(
     _In_ PSTARTUP_CALLBACK pfnCallback,
     _In_ PVOID parameter,
     _Out_ PVOID *ppUnregisterToken)
+{
+    return RegisterForRuntimeStartup3(dwProcessId, lpApplicationGroupId, NULL, pfnCallback, parameter, ppUnregisterToken);
+}
+
+//-----------------------------------------------------------------------------
+// Public API.
+//
+// RegisterForRuntimeStartup3 -- executes the callback when the coreclr runtime
+//      starts in the specified process. The callback is passed the proper ICorDebug
+//      instance for the version of the runtime or an error if something fails. This
+//      API works for launch and attach (and even the attach scenario if the runtime
+//      hasn't been loaded yet) equally on both xplat and Windows. The callback is
+//      always called on a separate thread. This API returns immediately.
+//
+//      The callback is invoked when the coreclr runtime module is loaded during early
+//      initialization. The runtime is blocked during initialization until the callback
+//      returns.
+//
+//      If the runtime is already loaded in the process (as in the normal attach case),
+//      the callback is executed and the runtime is not blocked.
+//
+//      The callback is always invoked on a separate thread and this API returns immediately.
+//
+//      Only the first coreclr module instance found in the target process is currently
+//      supported.
+//
+// dwProcessId -- process id of the target process
+// lpApplicationGroupId - A string representing the application group ID of a sandboxed
+//                        process running in Mac. Pass NULL if the process is not
+//                        running in a sandbox and other platforms.
+// pLibraryProvider - a callback for locating DBI and DAC
+// pfnCallback -- invoked when coreclr runtime starts
+// parameter -- data to pass to callback
+// ppUnregisterToken -- pointer to put the UnregisterForRuntimeStartup token.
+//
+//-----------------------------------------------------------------------------
+DLLEXPORT
+HRESULT
+RegisterForRuntimeStartup3(
+    _In_ DWORD dwProcessId,
+    _In_ LPCWSTR lpApplicationGroupId,
+    _In_ ICLRDebuggingLibraryProvider3* pLibraryProvider,
+    _In_ PSTARTUP_CALLBACK pfnCallback,
+    _In_ PVOID parameter,
+    _Out_ PVOID *ppUnregisterToken)
 {
     PUBLIC_CONTRACT;
 
@@ -753,7 +875,7 @@ RegisterForRuntimeStartupEx(
 
     HRESULT hr = S_OK;
 
-    RuntimeStartupHelper *helper = new (nothrow) RuntimeStartupHelper(dwProcessId, pfnCallback, parameter);
+    RuntimeStartupHelper *helper = new (nothrow) RuntimeStartupHelper(dwProcessId, pLibraryProvider, pfnCallback, parameter);
     if (helper == NULL)
     {
         hr = E_OUTOFMEMORY;
@@ -832,7 +954,7 @@ GetStartupNotificationEvent(
     if (phStartupEvent == NULL)
         return E_INVALIDARG;
 
-#ifndef TARGET_UNIX
+#ifdef TARGET_WINDOWS
     HRESULT hr;
     DWORD currentSessionId = 0, debuggeeSessionId = 0;
     if (!ProcessIdToSessionId(GetCurrentProcessId(), &currentSessionId))
@@ -900,9 +1022,32 @@ GetStartupNotificationEvent(
 #else
     *phStartupEvent = NULL;
     return E_NOTIMPL;
-#endif // TARGET_UNIX
+#endif // TARGET_WINDOWS
 }
-// Refer to clr\src\mscoree\mscorwks_ntdef.src.
+
+//
+// Returns true iff the module represents CoreClr.
+//
+static
+bool
+IsCoreClr(
+    const WCHAR* pModulePath)
+{
+    _ASSERTE(pModulePath != NULL);
+
+    //strip off everything up to and including the last slash in the path to get name
+    const WCHAR* pModuleName = pModulePath;
+    while(wcschr(pModuleName, DIRECTORY_SEPARATOR_CHAR_W) != NULL)
+    {
+        pModuleName = wcschr(pModuleName, DIRECTORY_SEPARATOR_CHAR_W);
+        pModuleName++; // pass the slash
+    }
+
+    // MAIN_CLR_MODULE_NAME_W gets changed for desktop builds, so we directly code against the CoreClr name.
+    return _wcsicmp(pModuleName, MAKEDLLNAME_W(W("coreclr"))) == 0;
+}
+
+// Refer to src\coreclr\dlls\mscoree\mscorwks_ntdef.src
 const WORD kOrdinalForMetrics = 2;
 
 //-----------------------------------------------------------------------------
@@ -911,12 +1056,13 @@ const WORD kOrdinalForMetrics = 2;
 // startup event for a coreclr.dll specified by its full path.
 //
 // Arguments:
-//   szTelestoFullPath - (in) full path of telesto
-//   pEngineMetricsOut - (out) filled in based on metrics from target telesto.
+//   wszModulePath - (in) full path of possible coreclr or single file module
+//   pEngineMetricsOut - (out; optional) filled in based on metrics from target runtime
+//   pClrInfoOut - (out; optional) filled in from the DotNetRuntimeInfo export for single-file apps
 //   pdwRVAContinueStartupEvent - (out; optional) return the RVA to the continue startup event
 //
 // Returns:
-//   Throwss on error.
+//   HRESULT
 //
 // Notes:
 //     When VS pops up the attach dialog box, it is actually enumerating all the processes on the machine
@@ -926,25 +1072,19 @@ const WORD kOrdinalForMetrics = 2;
 //     That's why we need to be extra careful reading coreclr.dll in this function.
 //-----------------------------------------------------------------------------
 static
-void
+HRESULT
 GetTargetCLRMetrics(
-    LPCWSTR szTelestoFullPath,
+    LPCWSTR wszModulePath,
     CLR_ENGINE_METRICS *pEngineMetricsOut,
-    DWORD *pdwRVAContinueStartupEvent = NULL)
+    ClrInfo* pClrInfoOut,
+    DWORD *pdwRVAContinueStartupEvent)
 {
-    CONTRACTL
-    {
-        THROWS;
-    }
-    CONTRACTL_END;
+    CONSISTENCY_CHECK(wszModulePath != NULL);
 
-    CONSISTENCY_CHECK(szTelestoFullPath != NULL);
-    CONSISTENCY_CHECK(pEngineMetricsOut != NULL);
-
-#ifndef TARGET_UNIX
+#ifdef TARGET_WINDOWS
     HRESULT hr = S_OK;
 
-    HandleHolder hCoreClrFile = WszCreateFile(szTelestoFullPath,
+    HandleHolder hCoreClrFile = WszCreateFile(wszModulePath,
                                               GENERIC_READ,
                                               FILE_SHARE_READ,
                                               NULL,                 // default security descriptor
@@ -953,32 +1093,32 @@ GetTargetCLRMetrics(
                                               NULL);
     if (hCoreClrFile == INVALID_HANDLE_VALUE)
     {
-        ThrowLastError();
+        return HRESULT_FROM_WIN32(GetLastError());
     }
 
     DWORD cbFileHigh = 0;
     DWORD cbFileLow = GetFileSize(hCoreClrFile, &cbFileHigh);
     if (cbFileLow == INVALID_FILE_SIZE)
     {
-        ThrowLastError();
+        return HRESULT_FROM_WIN32(GetLastError());
     }
 
     // A maximum size of 100 MB should be more than enough for coreclr.dll.
     if ((cbFileHigh != 0) || (cbFileLow > 0x6400000) || (cbFileLow == 0))
     {
-        ThrowHR(E_FAIL);
+        return E_FAIL;
     }
 
     HandleHolder hCoreClrMap = WszCreateFileMapping(hCoreClrFile, NULL, PAGE_READONLY, cbFileHigh, cbFileLow, NULL);
     if (hCoreClrMap == NULL)
     {
-        ThrowLastError();
+        return HRESULT_FROM_WIN32(GetLastError());
     }
 
     MapViewHolder hCoreClrMapView = MapViewOfFile(hCoreClrMap, FILE_MAP_READ, 0, 0, 0);
     if (hCoreClrMapView == NULL)
     {
-        ThrowLastError();
+        return HRESULT_FROM_WIN32(GetLastError());
     }
 
     // At this point we have read the file into the process, but be careful because it is flat, i.e. not mapped.
@@ -988,7 +1128,7 @@ GetTargetCLRMetrics(
     // Check the NT headers.
     if (!pedecoder.CheckNTFormat())
     {
-        ThrowHR(E_FAIL);
+        return E_FAIL;
     }
 
     // At this point we can safely read anything in the NT headers.
@@ -996,65 +1136,126 @@ GetTargetCLRMetrics(
     if (!pedecoder.HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_EXPORT) ||
         !pedecoder.CheckDirectoryEntry(IMAGE_DIRECTORY_ENTRY_EXPORT))
     {
-        ThrowHR(E_FAIL);
+        return E_FAIL;
     }
-    IMAGE_DATA_DIRECTORY * pExportDirectoryEntry = pedecoder.GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_EXPORT);
-
-    // At this point we can safely read the IMAGE_DATA_DIRECTORY of the export directory.
 
-    if (!pedecoder.CheckDirectory(pExportDirectoryEntry))
+    // If we are looking for the DotNetRuntimeInfo export for a single-file app, do this before looking for
+    // engine metrics export ordinal for a faster out of the module search loop. There are plenty of other 
+    // native modules with the metrics ordinal #2.
+    if (pClrInfoOut != NULL)
     {
-        ThrowHR(E_FAIL);
+        if (IsCoreClr(wszModulePath))
+        {
+            PEDecoder_ResourceCallbackFunction callback = ([](LPCWSTR lpName, LPCWSTR lpType, DWORD langid, BYTE* data, COUNT_T cbData, void* context) { 
+                CLR_DEBUG_RESOURCE* pDebugResource = (CLR_DEBUG_RESOURCE*)data;
+                ClrInfo* pClrInfo = (ClrInfo*)context;
+                if (cbData != sizeof(CLR_DEBUG_RESOURCE) || pDebugResource->dwVersion != 0 || pDebugResource->signature != CLR_ID_ONECORE_CLR)
+                {
+                    return false;
+                }
+                pClrInfo->IndexType = LIBRARY_PROVIDER_INDEX_TYPE::Identity; 
+                pClrInfo->DbiTimeStamp = pDebugResource->dwDbiTimeStamp;
+                pClrInfo->DbiSizeOfImage = pDebugResource->dwDbiSizeOfImage;
+                pClrInfo->DacTimeStamp = pDebugResource->dwDacTimeStamp;
+                pClrInfo->DacSizeOfImage = pDebugResource->dwDacSizeOfImage;
+                return true;
+            });
+            if (!pedecoder.EnumerateWin32Resources(W("CLRDEBUGINFO"), MAKEINTRESOURCEW(10), callback, pClrInfoOut))
+            {
+                return E_FAIL;
+            }
+        }
+        else
+        { 
+            PTR_VOID runtimeInfoExport = pedecoder.GetExport(RUNTIME_INFO_SIGNATURE);
+            if (runtimeInfoExport == NULL)
+            {
+                return E_FAIL;
+            }
+            RuntimeInfo* pRuntimeInfo = reinterpret_cast<RuntimeInfo*>(runtimeInfoExport);
+            if (strncmp(pRuntimeInfo->Signature, RUNTIME_INFO_SIGNATURE, sizeof(pRuntimeInfo->Signature)) != 0)
+            {
+                return E_FAIL;
+            }
+            if (pRuntimeInfo->Version <= 0)
+            {
+                return E_FAIL;
+            }
+            if (pRuntimeInfo->DbiModuleIndex[0] < (sizeof(DWORD) + sizeof(DWORD)) || pRuntimeInfo->DacModuleIndex[0] < (sizeof(DWORD) + sizeof(DWORD)))
+            {
+                return E_FAIL;
+            }
+            pClrInfoOut->IndexType = LIBRARY_PROVIDER_INDEX_TYPE::Identity; 
+            pClrInfoOut->DbiTimeStamp = *((DWORD*)&pRuntimeInfo->DbiModuleIndex[1]);
+            pClrInfoOut->DbiSizeOfImage = *((DWORD*)&pRuntimeInfo->DbiModuleIndex[5]);
+            pClrInfoOut->DacTimeStamp = *((DWORD*)&pRuntimeInfo->DacModuleIndex[1]);
+            pClrInfoOut->DacSizeOfImage = *((DWORD*)&pRuntimeInfo->DacModuleIndex[5]);
+        }
     }
-    IMAGE_EXPORT_DIRECTORY * pExportDir =
-        reinterpret_cast<IMAGE_EXPORT_DIRECTORY *>(pedecoder.GetDirectoryData(pExportDirectoryEntry));
-
-    // At this point we have checked that everything in the export directory is readable.
 
-    // Check to make sure the ordinal we have fits in the table in the export directory.
-    // The "base" here is like the starting index of the arrays in the export directory.
-    if ((pExportDir->Base > kOrdinalForMetrics) ||
-        (pExportDir->NumberOfFunctions < (kOrdinalForMetrics - pExportDir->Base)))
+    if (pEngineMetricsOut != NULL)
     {
-        ThrowHR(E_FAIL);
-    }
-    DWORD dwRealIndex = kOrdinalForMetrics - pExportDir->Base;
+        IMAGE_DATA_DIRECTORY * pExportDirectoryEntry = pedecoder.GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_EXPORT);
 
-    // Check that we can read the RVA at the element (specified by the ordinal) in the export address table.
-    // Then read the RVA to the CLR_ENGINE_METRICS.
-    if (!pedecoder.CheckRva(pExportDir->AddressOfFunctions, (dwRealIndex + 1) * sizeof(DWORD)))
-    {
-        ThrowHR(E_FAIL);
-    }
-    DWORD rvaMetrics = *reinterpret_cast<DWORD *>(
-        pedecoder.GetRvaData(pExportDir->AddressOfFunctions + dwRealIndex * sizeof(DWORD)));
+        // At this point we can safely read the IMAGE_DATA_DIRECTORY of the export directory.
 
-    // Make sure we can safely read the CLR_ENGINE_METRICS at the RVA we have retrieved.
-    if (!pedecoder.CheckRva(rvaMetrics, sizeof(*pEngineMetricsOut)))
-    {
-        ThrowHR(E_FAIL);
-    }
+        if (!pedecoder.CheckDirectory(pExportDirectoryEntry))
+        {
+            return E_FAIL;
+        }
 
-    // Finally, copy the CLR_ENGINE_METRICS into the output buffer.
-    CLR_ENGINE_METRICS * pMetricsInFile = reinterpret_cast<CLR_ENGINE_METRICS *>(pedecoder.GetRvaData(rvaMetrics));
-    *pEngineMetricsOut = *pMetricsInFile;
+        IMAGE_EXPORT_DIRECTORY * pExportDir =
+            reinterpret_cast<IMAGE_EXPORT_DIRECTORY *>(pedecoder.GetDirectoryData(pExportDirectoryEntry));
 
-    // At this point, we have retrieved the CLR_ENGINE_METRICS from the target process and
-    // stored it in output buffer.
-    if (pEngineMetricsOut->cbSize != sizeof(*pEngineMetricsOut))
-    {
-        ThrowHR(E_INVALIDARG);
+        // At this point we have checked that everything in the export directory is readable.
+    
+        // Check to make sure the ordinal we have fits in the table in the export directory.
+        // The "base" here is like the starting index of the arrays in the export directory.
+        if ((pExportDir->Base > kOrdinalForMetrics) || 
+            (pExportDir->NumberOfFunctions < (kOrdinalForMetrics - pExportDir->Base)))
+        {
+            return E_FAIL;
+        }
+        DWORD dwRealIndex = kOrdinalForMetrics - pExportDir->Base;
+
+        // Check that we can read the RVA at the element (specified by the ordinal) in the export address table.
+        // Then read the RVA to the CLR_ENGINE_METRICS.
+        if (!pedecoder.CheckRva(pExportDir->AddressOfFunctions, (dwRealIndex + 1) * sizeof(DWORD)))
+        {
+            return E_FAIL;
+        }
+        DWORD rvaMetrics = *reinterpret_cast<DWORD *>(
+           pedecoder.GetRvaData(pExportDir->AddressOfFunctions + dwRealIndex * sizeof(DWORD)));
+
+        // Make sure we can safely read the CLR_ENGINE_METRICS at the RVA we have retrieved.
+        if (!pedecoder.CheckRva(rvaMetrics, sizeof(*pEngineMetricsOut)))
+        {
+            return E_FAIL;
+        }
+
+        // Finally, copy the CLR_ENGINE_METRICS into the output buffer.
+        CLR_ENGINE_METRICS * pMetricsInFile = reinterpret_cast<CLR_ENGINE_METRICS *>(pedecoder.GetRvaData(rvaMetrics));
+        *pEngineMetricsOut = *pMetricsInFile;
+
+        // At this point, we have retrieved the CLR_ENGINE_METRICS from the target process and
+        // stored it in output buffer.
+        if (pEngineMetricsOut->cbSize != sizeof(*pEngineMetricsOut))
+        {
+            return E_INVALIDARG;
+        }
     }
 
     if (pdwRVAContinueStartupEvent != NULL)
     {
+        _ASSERTE(pEngineMetricsOut != NULL);
+
         // Note that the pointer stored in the CLR_ENGINE_METRICS is assuming that the DLL is loaded at its
         // preferred base address.  We need to translate that to an RVA.
         if (((SIZE_T)pEngineMetricsOut->phContinueStartupEvent < (SIZE_T)pedecoder.GetPreferredBase()) ||
             ((SIZE_T)pEngineMetricsOut->phContinueStartupEvent >
                 ((SIZE_T)pedecoder.GetPreferredBase() + pedecoder.GetVirtualSize())))
         {
-            ThrowHR(E_FAIL);
+            return E_FAIL;
         }
 
         DWORD rvaContinueStartupEvent =
@@ -1065,85 +1266,64 @@ GetTargetCLRMetrics(
         // falls in the loaded image.
         if ((rvaContinueStartupEvent + sizeof(HANDLE)) > pedecoder.GetVirtualSize())
         {
-            ThrowHR(E_FAIL);
+            return E_FAIL;
         }
 
         *pdwRVAContinueStartupEvent = rvaContinueStartupEvent;
     }
-
     // Holder will call FreeLibrary()
 #else
-    //TODO: So far on POSIX systems we only support one version of debugging interface
-    // in future we might want to detect it the same way we do it on Windows.
-    pEngineMetricsOut->cbSize = sizeof(*pEngineMetricsOut);
-    pEngineMetricsOut->dwDbiVersion = CorDebugLatestVersion;
-    pEngineMetricsOut->phContinueStartupEvent = NULL;
-
-    if (pdwRVAContinueStartupEvent != NULL)
+    if (pClrInfoOut != NULL)
     {
-        *pdwRVAContinueStartupEvent = NULL;
-    }
-#endif // TARGET_UNIX
-}
-
-// Returns true iff the module represents CoreClr.
-static
-bool
-IsCoreClr(
-    const WCHAR* pModulePath)
-{
-    _ASSERTE(pModulePath != NULL);
-
-    //strip off everything up to and including the last slash in the path to get name
-    const WCHAR* pModuleName = pModulePath;
-    while(wcschr(pModuleName, DIRECTORY_SEPARATOR_CHAR_W) != NULL)
-    {
-        pModuleName = wcschr(pModuleName, DIRECTORY_SEPARATOR_CHAR_W);
-        pModuleName++; // pass the slash
-    }
-
-    // MAIN_CLR_MODULE_NAME_W gets changed for desktop builds, so we directly code against the CoreClr name.
-    return _wcsicmp(pModuleName, MAKEDLLNAME_W(W("coreclr"))) == 0;
-}
+        if (IsCoreClr(wszModulePath))
+        {
+            // Get the runtime index info (build id) for Linux/MacOS
+            if (!TryGetBuildIdFromFile(wszModulePath, pClrInfoOut->RuntimeBuildId, MAX_BUILDID_SIZE, &pClrInfoOut->RuntimeBuildIdSize)) 
+            {
+                return E_FAIL;
+            }
+            pClrInfoOut->IndexType = LIBRARY_PROVIDER_INDEX_TYPE::Runtime; 
+        }
+        else
+        { 
+            RuntimeInfo runtimeInfo;
+            if (!TryReadSymbolFromFile(wszModulePath, RUNTIME_INFO_SIGNATURE, (BYTE*)&runtimeInfo, sizeof(RuntimeInfo)))
+            {
+                return E_FAIL;
+            }
+            if (strcmp(runtimeInfo.Signature, RUNTIME_INFO_SIGNATURE) != 0)
+            {
+                return E_FAIL;
+            }
+            pClrInfoOut->IndexType = LIBRARY_PROVIDER_INDEX_TYPE::Identity; 
 
-// Returns true iff the module sent is named CoreClr.dll and has the metrics expected in it's PE header.
-static
-bool
-IsCoreClrWithGoodHeader(
-    HANDLE hProcess,
-    HMODULE hModule)
-{
-    HRESULT hr = S_OK;
+            // The first byte is the number of bytes in the index
+            pClrInfoOut->DbiBuildIdSize = runtimeInfo.DbiModuleIndex[0];
+            memcpy_s(&pClrInfoOut->DbiBuildId, sizeof(pClrInfoOut->DbiBuildId), &(runtimeInfo.DbiModuleIndex[1]), pClrInfoOut->DbiBuildIdSize);
 
-    WCHAR modulePath[MAX_LONGPATH];
-    modulePath[0] = W('\0');
-    if(0 == GetModuleFileNameEx(hProcess, hModule, modulePath, MAX_LONGPATH))
-    {
-        return false;
+            pClrInfoOut->DacBuildIdSize = runtimeInfo.DacModuleIndex[0];
+            memcpy_s(&pClrInfoOut->DacBuildId, sizeof(pClrInfoOut->DacBuildId), &(runtimeInfo.DacModuleIndex[1]), pClrInfoOut->DacBuildIdSize);
+        }
     }
-    else
+
+    if (pEngineMetricsOut != NULL)
     {
-        modulePath[MAX_LONGPATH-1] = 0; // on older OS'es this doesn't get null terminated automatically on truncation
+        pEngineMetricsOut->cbSize = sizeof(*pEngineMetricsOut);
+        pEngineMetricsOut->dwDbiVersion = CorDebugVersion_4_0;
+        pEngineMetricsOut->phContinueStartupEvent = NULL;
     }
 
-    if (IsCoreClr(modulePath))
+    if (pdwRVAContinueStartupEvent != NULL)
     {
-        // We don't care about the particular error returned, only that
-        // what we tried wasn't a 'real' coreclr.dll.
-        EX_TRY
-        {
-            CLR_ENGINE_METRICS metricsStruct;
-            GetTargetCLRMetrics(modulePath, &metricsStruct); // throws
-
-            // If we got this far, then we think it's a good one.
-        }
-        EX_CATCH_HRESULT(hr);
-        return (hr == S_OK);
+        *pdwRVAContinueStartupEvent = 0;
     }
-
-    return false;
+#endif // TARGET_WINDOWS
+    return S_OK;
 }
 
+//
+// Enumerates all the modules in the process
+//
 static
 HRESULT
 EnumProcessModulesInternal(
@@ -1196,6 +1376,87 @@ EnumProcessModulesInternal(
     return S_OK;
 }
 
+//
+// Finds any coreclr or single-file app in the process
+//
+static
+HRESULT
+GetRuntime(
+    DWORD debuggeePID,
+    ClrRuntimeInfo& clrRuntimeInfo)
+{
+    HandleHolder hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, debuggeePID);
+    if (hProcess == NULL)
+    {
+        return HRESULT_FROM_WIN32(GetLastError());
+    }
+
+    // The modules in the array returned don't need to be closed
+    DWORD countModules;
+    ArrayHolder<HMODULE> modules = nullptr;
+    HRESULT hr = EnumProcessModulesInternal(hProcess, &countModules, &modules);
+    if (FAILED(hr))
+    {
+        return hr;
+    }
+
+    // This assumes we are only going to find one .NET runtime in the process. We do the module 
+    // enumeration only once because looking for single-file runtime info symbol is expensive.
+
+    WCHAR modulePath[MAX_LONGPATH];
+    for (DWORD i = 0; i < countModules; i++)
+    {
+        modulePath[0] = W('\0');
+        if (GetModuleFileNameEx(hProcess, modules[i], modulePath, MAX_LONGPATH) == 0)
+        {
+            continue;
+        }
+        else
+        {
+            modulePath[MAX_LONGPATH - 1] = 0; // on older OS'es this doesn't get null terminated automatically on truncation
+        }
+
+        // Get the DBI/DAC index info for the regular coreclr module or check if single-file app by looking for the 
+        // DotNetRuntimeInfo export. We need to get the metrics too because that is required to get the startup event.
+        DWORD rvaContinueStartupEvent = 0;
+        hr = GetTargetCLRMetrics(modulePath, &clrRuntimeInfo.EngineMetrics, &clrRuntimeInfo.ClrInfo, &rvaContinueStartupEvent);
+        if (SUCCEEDED(hr))
+        {
+            clrRuntimeInfo.ModuleHandle = modules[i];
+            EX_TRY
+            {
+                clrRuntimeInfo.ClrInfo.RuntimeModulePath.Set(modulePath);
+            }
+            EX_CATCH_HRESULT(hr);
+#ifdef TARGET_WINDOWS
+            if (rvaContinueStartupEvent != 0)
+            {
+                HANDLE continueEvent = NULL;
+                SIZE_T nBytesRead;
+                if (ReadProcessMemory(hProcess, ((BYTE*)modules[i]) + rvaContinueStartupEvent, &continueEvent, sizeof(continueEvent), &nBytesRead))
+                {
+                    if (continueEvent != NULL && continueEvent != INVALID_HANDLE_VALUE)
+                    {
+                        if (DuplicateHandle(hProcess, continueEvent, GetCurrentProcess(), &continueEvent, EVENT_MODIFY_STATE, FALSE, 0))
+                        {
+                            clrRuntimeInfo.ContinueStartupEvent = continueEvent;
+                        }
+                    }
+                    else 
+                    {
+                        clrRuntimeInfo.ContinueStartupEvent = continueEvent;
+                    }
+                }
+            }
+#endif
+            return S_OK;
+        }
+    }
+
+    // Didn't find any runtimes and there were no failures
+    return S_FALSE;
+}
+
 //-----------------------------------------------------------------------------
 // Public API.
 //
@@ -1228,119 +1489,49 @@ EnumerateCLRs(
 
     // All out params must be non-NULL.
     if ((ppHandleArrayOut == NULL) || (ppStringArrayOut == NULL) || (pdwArrayLengthOut == NULL))
+    {
         return E_INVALIDARG;
+    }
 
-    HandleHolder hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, debuggeePID);
-    if (NULL == hProcess)
-        return E_FAIL;
+    *pdwArrayLengthOut = 0;
+    *ppHandleArrayOut = NULL;
+    *ppStringArrayOut = NULL;
 
-    // The modules in the array returned don't need to be closed
-    DWORD countModules;
-    ArrayHolder<HMODULE> modules = nullptr;
-    HRESULT hr = EnumProcessModulesInternal(hProcess, &countModules, &modules);
+    ClrRuntimeInfo clrRuntimeInfo;
+    HRESULT hr = GetRuntime(debuggeePID, clrRuntimeInfo);
     if (FAILED(hr))
     {
         return hr;
     }
 
-    //
-    // count the number of coreclr.dll entries
-    //
-    DWORD count = 0;
-    for(DWORD i = 0; i < countModules; i++)
-    {
-        if (IsCoreClrWithGoodHeader(hProcess, modules[i]))
-        {
-            count++;
-        }
-    }
-
-    // If we didn't find anything, no point in continuing.
-    if (count == 0)
+    // S_FALSE means there are no runtimes and no falures
+    if (hr == S_OK)
     {
-        *ppHandleArrayOut = NULL;
-        *ppStringArrayOut = NULL;
-        *pdwArrayLengthOut = 0;
-
-        return S_OK;
-    }
-
-    size_t cbEventArrayData     = sizeof(HANDLE) * count;               // event array data
-    size_t cbStringArrayData    = sizeof(LPWSTR) * count;               // string array data
-    size_t cbStringData         = sizeof(WCHAR)  * count * MAX_LONGPATH;    // strings data
-    size_t cbBuffer             = cbEventArrayData + cbStringArrayData + cbStringData;
-
-    BYTE* pOutBuffer = new (nothrow) BYTE[cbBuffer];
-    if (NULL == pOutBuffer)
-        return E_OUTOFMEMORY;
+        // Allocate the buffers for one runtime
+        size_t cbEventArrayData     = sizeof(HANDLE);
+        size_t cbStringArrayData    = sizeof(LPWSTR);
+        size_t cbStringData         = sizeof(WCHAR) * MAX_LONGPATH;
+        size_t cbBuffer             = cbEventArrayData + cbStringArrayData + cbStringData;
 
-    ZeroMemory(pOutBuffer, cbBuffer);
-
-    HANDLE* pEventArray     = (HANDLE*) &pOutBuffer[0];
-    LPWSTR* pStringArray    = (LPWSTR*) &pOutBuffer[cbEventArrayData];
-    WCHAR*  pStringData     = (WCHAR*)  &pOutBuffer[cbEventArrayData + cbStringArrayData];
-    DWORD idx = 0;
-
-    // There's no guarantee that another coreclr hasn't loaded already anyhow,
-    // so if we get the corner case that the second time through we enumerate
-    // more coreclrs, just ignore the extras.
-    // This mismatch could happen when
-    // a) take module shapshot
-    // b) underlying file is opened for exclusive access/deleted/moved/ACL'd etc so we can't open it
-    // c) count is determined
-    // d) file is closed/copied/moved/ACL'd etc so we can find/open it again
-    // e) this loop runs
-    // Thus the loop checks idx < count
-
-    for(DWORD i = 0; i < countModules && idx < count; i++)
-    {
-        if (IsCoreClrWithGoodHeader(hProcess, modules[i]))
+        BYTE* pOutBuffer = new (nothrow) BYTE[cbBuffer];
+        if (NULL == pOutBuffer)
         {
-            // fill in path
-            pStringArray[idx] = &pStringData[idx * MAX_LONGPATH];
-            GetModuleFileNameEx(hProcess, modules[i], pStringArray[idx], MAX_LONGPATH);
-
-#ifndef TARGET_UNIX
-            // fill in event handle -- if GetContinueStartupEvent fails, it will still return
-            // INVALID_HANDLE_VALUE in hContinueStartupEvent, which is what we want.  we don't
-            // want to bail out of the enumeration altogether if we can't get an event from
-            // one telesto.
-
-            HANDLE hContinueStartupEvent = INVALID_HANDLE_VALUE;
-            HRESULT hr = GetContinueStartupEvent(debuggeePID, pStringArray[idx], &hContinueStartupEvent);
-            pEventArray[idx] = hContinueStartupEvent;
-#else
-            pEventArray[idx] = NULL;
-#endif // TARGET_UNIX
-
-            idx++;
+            return E_OUTOFMEMORY;
         }
-    }
+        ZeroMemory(pOutBuffer, cbBuffer);
 
-    // Patch things up so CloseCLREnumeration() can still have it's
-    // pointer arithmatic checks succeed, and the user doesn't see a 'dead' entry.
-    // Specifically, it's expected that pEventArray and pStringArray point to the
-    // same contiguous chunk of memory so that pStringArray == pEventArray[*pdwArrayLengthOut].
-    // This is expected to be a very rare case.
-    if (idx < count)
-    {
-        // Move the string pointers back.
-        LPWSTR* pSATemp = (LPWSTR*)&pOutBuffer[sizeof(HANDLE)*idx];
-        for (DWORD i = 0; i < idx; i++)
-        {
-            pSATemp[i] = pStringArray[i];
-        }
+        HANDLE* pEventArray = (HANDLE*) &pOutBuffer[0];
+        pEventArray[0] = clrRuntimeInfo.ContinueStartupEvent;
 
-        // Fix up string array pointer.
-        pStringArray = (LPWSTR*)&pOutBuffer[sizeof(HANDLE)*idx];
+        LPWSTR* pStringArray = (LPWSTR*) &pOutBuffer[cbEventArrayData];
+        pStringArray[0] = (WCHAR*) &pOutBuffer[cbEventArrayData + cbStringArrayData];
+        wcscpy_s(pStringArray[0], MAX_LONGPATH, clrRuntimeInfo.ClrInfo.RuntimeModulePath);
 
-        // Strings themselves don't need moved.
+        *pdwArrayLengthOut = 1;
+        *ppHandleArrayOut = pEventArray;
+        *ppStringArrayOut = pStringArray;
     }
 
-    *ppHandleArrayOut = pEventArray;
-    *ppStringArrayOut = pStringArray;
-    *pdwArrayLengthOut = idx;
-
     return S_OK;
 }
 
@@ -1372,7 +1563,7 @@ CloseCLREnumeration(
     if ((pHandleArray + dwArrayLength) != (HANDLE*)pStringArray)
         return E_INVALIDARG;
 
-#ifndef TARGET_UNIX
+#ifdef TARGET_WINDOWS
     for (DWORD i = 0; i < dwArrayLength; i++)
     {
         HANDLE hTemp = pHandleArray[i];
@@ -1382,7 +1573,7 @@ CloseCLREnumeration(
             CloseHandle(hTemp);
         }
     }
-#endif // TARGET_UNIX
+#endif // TARGET_WINDOWS
 
     delete[] pHandleArray;
     return S_OK;
@@ -1411,7 +1602,7 @@ GetRemoteModuleBaseAddress(
     HandleHolder hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
     if (NULL == hProcess)
     {
-        ThrowHR(E_FAIL);
+        ThrowHR(HRESULT_FROM_WIN32(GetLastError()));
     }
 
     // The modules in the array returned don't need to be closed
@@ -1517,7 +1708,6 @@ CreateVersionStringFromModule(
     }
     else if (pBuffer != NULL)
     {
-
         HRESULT hr = S_OK;
         EX_TRY
         {
@@ -1525,17 +1715,20 @@ CreateVersionStringFromModule(
             BYTE* hmodTargetCLR = NULL;
             CLR_ENGINE_METRICS metricsStruct;
 
-            GetTargetCLRMetrics(szModuleName, &metricsStruct); // throws
-            dbiVersion = (CorDebugInterfaceVersion) metricsStruct.dwDbiVersion;
-
-            hmodTargetCLR = GetRemoteModuleBaseAddress(pidDebuggee, szModuleName); // throws
-            if (hmodTargetCLR == NULL)
+            hr = GetTargetCLRMetrics(szModuleName, &metricsStruct);
+            if (SUCCEEDED(hr))
             {
-                hr = COR_E_FILENOTFOUND;
-            }
-            else
-            {
-                swprintf_s(pBuffer, cchBuffer, c_versionStrFormat, dbiVersion, pidDebuggee, hmodTargetCLR);
+                dbiVersion = (CorDebugInterfaceVersion) metricsStruct.dwDbiVersion;
+
+                hmodTargetCLR = GetRemoteModuleBaseAddress(pidDebuggee, szModuleName); // throws
+                if (hmodTargetCLR == NULL)
+                {
+                    hr = COR_E_FILENOTFOUND;
+                }
+                else
+                {
+                    swprintf_s(pBuffer, cchBuffer, c_versionStrFormat, dbiVersion, pidDebuggee, hmodTargetCLR);
+                }
             }
         }
         EX_CATCH_HRESULT(hr);
@@ -1627,8 +1820,13 @@ GetDbiFilenameNextToRuntime(
     // Step 1: (pid, hmodule) --> full path
     //
     HandleHolder hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pidDebuggee);
+    if (hProcess == NULL)
+    {
+        ThrowHR(HRESULT_FROM_WIN32(GetLastError()));
+    }
+
     WCHAR modulePath[MAX_LONGPATH];
-    if(0 == GetModuleFileNameEx(hProcess, hmodTargetCLR, modulePath, MAX_LONGPATH))
+    if (0 == GetModuleFileNameEx(hProcess, hmodTargetCLR, modulePath, MAX_LONGPATH))
     {
         ThrowHR(E_FAIL);
     }
@@ -1676,7 +1874,7 @@ CheckDbiAndRuntimeVersion(
     SString & szFullDbiPath,
     SString & szFullCoreClrPath)
 {
-#ifndef TARGET_UNIX
+#ifdef TARGET_WINDOWS
     DWORD dwDbiVersionMS = 0;
     DWORD dwDbiVersionLS = 0;
     DWORD dwCoreClrVersionMS = 0;
@@ -1697,7 +1895,33 @@ CheckDbiAndRuntimeVersion(
     }
 #else
     return true;
-#endif // TARGET_UNIX
+#endif // TARGET_WINDOWS
+}
+
+//-----------------------------------------------------------------------------
+// Public API.
+// Superceded by CreateDebuggingInterfaceFromVersionEx in SLv4.
+// Given a version string, create the matching mscordbi.dll for it.
+// Create a managed debugging interface for the specified version.
+//
+// Parameters:
+//    szDebuggeeVersion - the version of the debuggee. This will map to a version of mscordbi.dll
+//    ppCordb - the outparameter used to return the debugging interface object.
+//
+// Return:
+//  S_OK on success. *ppCordb will be non-null.
+//  CORDBG_E_INCOMPATIBLE_PROTOCOL - if the proper DBI is not available. This can be a very common error if
+//    the right debug pack is not installed.
+//  else Error. (*ppCordb will be null)
+//-----------------------------------------------------------------------------
+DLLEXPORT
+HRESULT
+CreateDebuggingInterfaceFromVersion(
+    _In_ LPCWSTR szDebuggeeVersion,
+    _Out_ IUnknown ** ppCordb
+)
+{
+    return CreateDebuggingInterfaceFromVersion3(CorDebugVersion_2_0, szDebuggeeVersion, NULL, NULL, ppCordb);
 }
 
 //-----------------------------------------------------------------------------
@@ -1723,7 +1947,7 @@ CreateDebuggingInterfaceFromVersionEx(
     _In_ LPCWSTR szDebuggeeVersion,
     _Out_ IUnknown ** ppCordb)
 {
-    return CreateDebuggingInterfaceFromVersion2(iDebuggerVersion, szDebuggeeVersion, NULL, ppCordb);
+    return CreateDebuggingInterfaceFromVersion3(iDebuggerVersion, szDebuggeeVersion, NULL, NULL, ppCordb);
 }
 
 //-----------------------------------------------------------------------------
@@ -1752,90 +1976,119 @@ CreateDebuggingInterfaceFromVersion2(
     _In_ LPCWSTR szDebuggeeVersion,
     _In_ LPCWSTR szApplicationGroupId,
     _Out_ IUnknown ** ppCordb)
+{
+    return CreateDebuggingInterfaceFromVersion3(iDebuggerVersion, szDebuggeeVersion, szApplicationGroupId, NULL, ppCordb);
+}
+
+//-----------------------------------------------------------------------------
+// Public API.
+// Given a version string, create the matching mscordbi.dll for it.
+// Create a managed debugging interface for the specified version.
+//
+// Parameters:
+//    iDebuggerVersion - the version of interface the debugger (eg, Cordbg) expects.
+//    szDebuggeeVersion - the version of the debuggee. This will map to a version of mscordbi.dll
+//    lpApplicationGroupId - a string representing the application group ID of a sandboxed
+//                           process running in Mac. Pass NULL if the process is not
+//                           running in a sandbox and other platforms.
+//    pLibraryProvider - a callback for locating DBI and DAC
+//    ppCordb - the outparameter used to return the debugging interface object.
+//
+// Return:
+//  S_OK on success. *ppCordb will be non-null.
+//  CORDBG_E_INCOMPATIBLE_PROTOCOL - if the proper DBI is not available. This can be a very common error if
+//    the right debug pack is not installed.
+//  else Error. (*ppCordb will be null)
+//-----------------------------------------------------------------------------
+DLLEXPORT
+HRESULT
+CreateDebuggingInterfaceFromVersion3(
+    _In_ int iDebuggerVersion,
+    _In_ LPCWSTR szDebuggeeVersion,
+    _In_ LPCWSTR szApplicationGroupId,
+    _In_ ICLRDebuggingLibraryProvider3* pLibraryProvider,
+    _Out_ IUnknown ** ppCordb)
 {
     PUBLIC_CONTRACT;
 
-    HRESULT hrIgnore = S_OK; // ignored HResult
+    IUnknown* pCordb = NULL;
+    SString szFullDbiPath;
+    SString szFullDacPath;
     HRESULT hr = S_OK;
-    HMODULE hMod = NULL;
-    IUnknown * pCordb = NULL;
 
-    LOG((LF_CORDB, LL_EVERYTHING, "Calling CreateDebuggerInterfaceFromVersion, ver=%S\n", szDebuggeeVersion));
+    LOG((LF_CORDB, LL_EVERYTHING, "Calling CreateDebuggerInterfaceFromVersion3, ver=%S\n", szDebuggeeVersion));
 
     if ((szDebuggeeVersion == NULL) || (ppCordb == NULL))
     {
         hr = E_INVALIDARG;
-        goto Exit;
+        goto exit;
     }
 
     //
     // Step 1: Parse version information into internal data structures
     //
 
-    CorDebugInterfaceVersion iTargetVersion;  // the CorDebugInterfaceVersion (CorDebugVersion_2_0)
-    DWORD pidDebuggee;     // OS process ID of the debuggee
-    HMODULE hmodTargetCLR; // module of Telesto in target (the clrInstanceId)
+    CorDebugInterfaceVersion iTargetVersion;    // the CorDebugInterfaceVersion (CorDebugVersion_2_0)
+    DWORD pidDebuggee;                          // OS process ID of the debuggee
+    HMODULE hmodTargetCLR;                      // module of Telesto in target (the clrInstanceId)
 
     hr = ParseVersionString(szDebuggeeVersion, &iTargetVersion, &pidDebuggee, &hmodTargetCLR);
     if (FAILED(hr))
-        goto Exit;
+    {
+        goto exit;
+    }
 
     //
-    // Step 2:  Find the proper dbi module (mscordbi) and load it.
+    // Step 2:  Find the proper dbi module (mscordbi)
     //
 
-    // Check for dbi next to target CLR.
-    // This will be very common for internal developer setups, but not common in end-user setups.
     EX_TRY
     {
-        SString szFullDbiPath;
         SString szFullCoreClrPath;
-
         GetDbiFilenameNextToRuntime(pidDebuggee, hmodTargetCLR, szFullDbiPath, szFullCoreClrPath);
 
-        if (!CheckDbiAndRuntimeVersion(szFullDbiPath, szFullCoreClrPath))
+        if (pLibraryProvider != NULL)
+        { 
+            // Get the DBI/DAC index info for regular and single-file apps
+            ClrInfo clrInfo;
+            hr = GetTargetCLRMetrics(szFullCoreClrPath, NULL, &clrInfo, NULL);
+            if (SUCCEEDED(hr))
+            {
+                clrInfo.RuntimeModulePath.Set(szFullCoreClrPath);
+                hr = CLRDebuggingImpl::ProvideLibraries(clrInfo, pLibraryProvider, szFullDbiPath, szFullDacPath);
+            }
+        }
+        else
         {
-            hr = CORDBG_E_INCOMPATIBLE_PROTOCOL;
-            goto Exit;
+            // Check for dbi next to target CLR.
+            // This will be very common for internal developer setups, but not common in end-user setups.
+            if (!CheckDbiAndRuntimeVersion(szFullDbiPath, szFullCoreClrPath))
+            {
+                hr = CORDBG_E_INCOMPATIBLE_PROTOCOL;
+                goto exit;
+            }
         }
-
-        // We calculated where dbi would be, but haven't yet verified if it's there.
-        // Try to load it. We're using this to check for file existence.
-
-        // Issue:951525: coreclr mscordbi load fails on downlevel OS since LoadLibraryEx can't find
-        // dependent forwarder DLLs. Force LoadLibrary to look for dependencies in szFullDbiPath plus the default
-        // search paths.
-#ifndef TARGET_UNIX
-        hMod = WszLoadLibraryEx(szFullDbiPath, NULL, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
-#else
-        hMod = LoadLibraryExW(szFullDbiPath, NULL, 0);
-#endif
     }
-    EX_CATCH_HRESULT(hrIgnore); // failure leaves hMod null
+    EX_CATCH_HRESULT(hr);
 
-    // Couldn't find Dbi, likely because the right debug pack is not installed. Failure.
-    if (NULL == hMod)
+    if (FAILED(hr))
     {
         // Check for the following two HRESULTs and return them specifically.  These are returned by
         // CreateToolhelp32Snapshot() and could be transient errors.  The debugger may choose to retry.
-        if ((hrIgnore == HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY)) || (hrIgnore == HRESULT_FROM_WIN32(ERROR_BAD_LENGTH)))
-        {
-            hr = hrIgnore;
-        }
-        else
+        if ((hr != HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY)) && (hr != HRESULT_FROM_WIN32(ERROR_BAD_LENGTH)))
         {
             hr = CORDBG_E_DEBUG_COMPONENT_MISSING;
         }
-        goto Exit;
+        goto exit;
     }
 
     //
-    // Step 3: Now that module is loaded, instantiate an ICorDebug.
+    // Step 3: Load DBI and instantiate an ICorDebug instance.
     //
-    hr = CreateCoreDbg(hmodTargetCLR, hMod, pidDebuggee, szApplicationGroupId, iDebuggerVersion, &pCordb);
+    hr = CreateCoreDbg(hmodTargetCLR, pidDebuggee, szFullDbiPath, szFullDacPath, szApplicationGroupId, iDebuggerVersion, &pCordb);
     _ASSERTE((pCordb == NULL) == FAILED(hr));
 
-Exit:
+exit:
     if (FAILED(hr))
     {
         if (pCordb != NULL)
@@ -1843,12 +2096,6 @@ Exit:
             pCordb->Release();
             pCordb = NULL;
         }
-
-        if (hMod != NULL)
-        {
-            _ASSERTE(pCordb == NULL);
-            FreeLibrary(hMod);
-        }
     }
 
     // Set our outparam.
@@ -1861,103 +2108,6 @@ Exit:
     return hr;
 }
 
-//-----------------------------------------------------------------------------
-// Public API.
-// Superceded by CreateDebuggingInterfaceFromVersionEx in SLv4.
-// Given a version string, create the matching mscordbi.dll for it.
-// Create a managed debugging interface for the specified version.
-//
-// Parameters:
-//    szDebuggeeVersion - the version of the debuggee. This will map to a version of mscordbi.dll
-//    ppCordb - the outparameter used to return the debugging interface object.
-//
-// Return:
-//  S_OK on success. *ppCordb will be non-null.
-//  CORDBG_E_INCOMPATIBLE_PROTOCOL - if the proper DBI is not available. This can be a very common error if
-//    the right debug pack is not installed.
-//  else Error. (*ppCordb will be null)
-//-----------------------------------------------------------------------------
-DLLEXPORT
-HRESULT
-CreateDebuggingInterfaceFromVersion(
-    _In_ LPCWSTR szDebuggeeVersion,
-    _Out_ IUnknown ** ppCordb
-)
-{
-    PUBLIC_CONTRACT;
-
-    return CreateDebuggingInterfaceFromVersionEx(CorDebugVersion_2_0, szDebuggeeVersion, ppCordb);
-}
-
-#ifndef TARGET_UNIX
-
-//------------------------------------------------------------------------------
-// Manually retrieves the "continue startup" event from the correct CLR instance
-// in the target process.
-//
-// Arguments:
-//    debuggeePID - (in) OS Process ID of debuggee
-//    szTelestoFullPath - (in) full path to telesto within the process.
-//    phContinueStartupEvent - (out)
-//
-// Returns:
-//   S_OK on success.
-//------------------------------------------------------------------------------
-HRESULT
-GetContinueStartupEvent(
-    DWORD debuggeePID,
-    LPCWSTR szTelestoFullPath,
-    _Out_ HANDLE* phContinueStartupEvent)
-{
-    if ((phContinueStartupEvent == NULL) || (szTelestoFullPath == NULL))
-        return E_INVALIDARG;
-
-    HRESULT hr = S_OK;
-    EX_TRY
-    {
-        *phContinueStartupEvent = INVALID_HANDLE_VALUE;
-
-        DWORD  dwCoreClrContinueEventOffset = 0;
-        CLR_ENGINE_METRICS metricsStruct;
-
-        GetTargetCLRMetrics(szTelestoFullPath, &metricsStruct, &dwCoreClrContinueEventOffset); // throws
-
-
-        BYTE* pbBaseAddress = GetRemoteModuleBaseAddress(debuggeePID, szTelestoFullPath); // throws
-
-
-        HandleHolder hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, debuggeePID);
-        if (NULL == hProcess)
-            ThrowHR(E_FAIL);
-
-        HANDLE continueEvent = NULL;
-
-        SIZE_T nBytesRead;
-        if (!ReadProcessMemory(hProcess, pbBaseAddress + dwCoreClrContinueEventOffset, &continueEvent,
-            sizeof(continueEvent), &nBytesRead))
-        {
-            ThrowHR(E_FAIL);
-        }
-
-        if (NULL != continueEvent && INVALID_HANDLE_VALUE != continueEvent)
-        {
-            if (!DuplicateHandle(hProcess, continueEvent, GetCurrentProcess(), &continueEvent,
-                EVENT_MODIFY_STATE, FALSE, 0))
-            {
-                ThrowHR(E_FAIL);
-            }
-        }
-
-        *phContinueStartupEvent = continueEvent;
-    }
-    EX_CATCH_HRESULT(hr)
-    return hr;
-}
-
-#endif // !TARGET_UNIX
-
-#include "debugshim.h"
-
 //-----------------------------------------------------------------------------
 // Public API.
 //
@@ -1984,9 +2134,7 @@ CLRCreateInstance(
     if (clsid != CLSID_CLRDebugging || riid != IID_ICLRDebugging)
         return E_NOINTERFACE;
 
-    GUID skuId = CLR_ID_ONECORE_CLR;
-
-    CLRDebuggingImpl *pDebuggingImpl = new (nothrow) CLRDebuggingImpl(skuId);
+    CLRDebuggingImpl *pDebuggingImpl = new (nothrow) CLRDebuggingImpl(CLR_ID_ONECORE_CLR);
     if (NULL == pDebuggingImpl)
         return E_OUTOFMEMORY;
 
index 004fd2a38128f21f53ca1499e27a232545feea42..34c71e5152386305890361dd110565ed86058aba 100644 (file)
@@ -6,6 +6,7 @@
 //*****************************************************************************
 
 #include <windows.h>
+#include "metahost.h"
 
 typedef VOID (*PSTARTUP_CALLBACK)(IUnknown *pCordb, PVOID parameter, HRESULT hr);
 
@@ -41,6 +42,15 @@ RegisterForRuntimeStartupEx(
     _In_ PVOID parameter,
     _Out_ PVOID *ppUnregisterToken);
 
+EXTERN_C HRESULT
+RegisterForRuntimeStartup3(
+    _In_ DWORD dwProcessId,
+    _In_ LPCWSTR szApplicationGroupId,
+    _In_ ICLRDebuggingLibraryProvider3* pLibraryProvider,
+    _In_ PSTARTUP_CALLBACK pfnCallback,
+    _In_ PVOID parameter,
+    _Out_ PVOID *ppUnregisterToken);
+
 EXTERN_C HRESULT
 UnregisterForRuntimeStartup(
     _In_ PVOID pUnregisterToken);
@@ -70,15 +80,18 @@ CreateVersionStringFromModule(
     _In_ DWORD cchBuffer,
     _Out_ DWORD* pdwLength);
 
+EXTERN_C HRESULT
+CreateDebuggingInterfaceFromVersion(
+    _In_ LPCWSTR szDebuggeeVersion,
+    _Out_ IUnknown ** ppCordb);
+
 EXTERN_C HRESULT
 CreateDebuggingInterfaceFromVersionEx(
     _In_ int iDebuggerVersion,
     _In_ LPCWSTR szDebuggeeVersion,
     _Out_ IUnknown ** ppCordb);
 
-EXTERN_C
-DLLEXPORT
-HRESULT
+EXTERN_C HRESULT
 CreateDebuggingInterfaceFromVersion2(
     _In_ int iDebuggerVersion,
     _In_ LPCWSTR szDebuggeeVersion,
@@ -86,6 +99,9 @@ CreateDebuggingInterfaceFromVersion2(
     _Out_ IUnknown ** ppCordb);
 
 EXTERN_C HRESULT
-CreateDebuggingInterfaceFromVersion(
+CreateDebuggingInterfaceFromVersion3(
+    _In_ int iDebuggerVersion,
     _In_ LPCWSTR szDebuggeeVersion,
+    _In_ LPCWSTR szApplicationGroupId,
+    _In_ ICLRDebuggingLibraryProvider3* pLibraryProvider,
     _Out_ IUnknown ** ppCordb);
index 2e254ab9d5fe8f6fd2dc5685c0f5d2fc59b80435..8b6572e1f08ac18725f2962eecb9b700b2c3d71e 100644 (file)
@@ -2,17 +2,19 @@
 ; The .NET Foundation licenses this file to you under the MIT license.
 
 EXPORTS
-        CreateProcessForLaunch
-        ResumeProcess
-        CloseResumeHandle
-        RegisterForRuntimeStartup
-       RegisterForRuntimeStartupEx
-        UnregisterForRuntimeStartup
-       GetStartupNotificationEvent
-       EnumerateCLRs
-       CloseCLREnumeration
-       CreateVersionStringFromModule
-       CreateDebuggingInterfaceFromVersion
-       CreateDebuggingInterfaceFromVersionEx
-       CreateDebuggingInterfaceFromVersion2
-       CLRCreateInstance
+    CreateProcessForLaunch
+    ResumeProcess
+    CloseResumeHandle
+    RegisterForRuntimeStartup
+    RegisterForRuntimeStartupEx
+    RegisterForRuntimeStartup3
+    UnregisterForRuntimeStartup
+    GetStartupNotificationEvent
+    EnumerateCLRs
+    CloseCLREnumeration
+    CreateVersionStringFromModule
+    CreateDebuggingInterfaceFromVersion
+    CreateDebuggingInterfaceFromVersionEx
+    CreateDebuggingInterfaceFromVersion2
+    CreateDebuggingInterfaceFromVersion3
+    CLRCreateInstance
index 189d87dac6c0e1867045b541591990cb79ec052c..6cc64432f642c672285871a1dc9a2f1ded0bdfa9 100644 (file)
@@ -87,7 +87,7 @@
   </PropertyGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <ClCompile>
-      <AdditionalIncludeDirectories>$(RepoRoot)artifacts\obj;$(RepoRoot)src;$(RepoRoot)src\pal\prebuilt\inc;$(RepoRoot)src\inc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>$(RepoRoot)artifacts\obj;$(RepoRoot)src\shared;$(RepoRoot)src\shared\inc;$(RepoRoot)src\shared\pal\prebuilt\inc;$(RepoRoot)src\shared\pal\inc;$(RepoRoot)src\shared\pal\inc\rt;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <AdditionalOptions>%(AdditionalOptions) /guard:ehcont /Zm200 /Zc:strictStrings /w34092 /w34121 /w34125 /w34130 /w34132 /w34212 /w34530 /w35038 /w44177 /ZH:SHA_256 /source-charset:utf-8 /homeparams</AdditionalOptions>
       <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
       <WarningLevel>Level3</WarningLevel>
       <PreprocessorDefinitions>WIN32;_WINDOWS;DEBUG;_DEBUG;_DBG;URTBLDENV_FRIENDLY=Debug;BUILDENV_DEBUG=1;HOST_AMD64;HOST_64BIT;HOST_WINDOWS;_FILE_OFFSET_BITS=64;TARGET_AMD64;TARGET_64BIT;TARGET_WINDOWS;_AMD64_;_WIN64;AMD64;BIT64=1;_TARGET_64BIT_=1;_TARGET_AMD64_=1;DBG_TARGET_64BIT=1;DBG_TARGET_AMD64=1;DBG_TARGET_WIN64=1;_WIN32;WINVER=0x0602;_WIN32_WINNT=0x0602;WIN32_LEAN_AND_MEAN=1;_CRT_SECURE_NO_WARNINGS;UNICODE;_UNICODE;FEATURE_COMINTEROP;FEATURE_HIJACK;FEATURE_NO_HOST;SELF_NO_HOST;_BLD_CLR;FX_VER_INTERNALNAME_STR=dbgshim.dll;CMAKE_INTDIR="Debug";dbgshim_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <ObjectFileName>$(IntDir)</ObjectFileName>
+      <BrowseInformation>true</BrowseInformation>
     </ClCompile>
     <ResourceCompile>
       <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;DEBUG;_DBG;URTBLDENV_FRIENDLY=Debug;BUILDENV_DEBUG=1;HOST_AMD64;HOST_64BIT;HOST_WINDOWS;_FILE_OFFSET_BITS=64;TARGET_AMD64;TARGET_64BIT;TARGET_WINDOWS;_AMD64_;_WIN64;AMD64;BIT64=1;_TARGET_64BIT_=1;_TARGET_AMD64_=1;DBG_TARGET_64BIT=1;DBG_TARGET_AMD64=1;DBG_TARGET_WIN64=1;_WIN32;WINVER=0x0602;_WIN32_WINNT=0x0602;WIN32_LEAN_AND_MEAN=1;_CRT_SECURE_NO_WARNINGS;UNICODE;_UNICODE;;FEATURE_COMINTEROP;FEATURE_HIJACK;FEATURE_NO_HOST;SELF_NO_HOST;_BLD_CLR;FX_VER_INTERNALNAME_STR=dbgshim.dll;CMAKE_INTDIR=\"Debug\";dbgshim_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     <ProjectReference>
       <LinkLibraryDependencies>false</LinkLibraryDependencies>
     </ProjectReference>
+    <Bscmake>
+      <PreserveSbr>true</PreserveSbr>
+    </Bscmake>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Checked|x64'">
     <ClCompile>
     <None Include="CMakeLists.txt" />
   </ItemGroup>
   <ItemGroup>
+    <ClInclude Include="dbgshim.h" />
     <ClInclude Include="debugshim.h" />
   </ItemGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
     <Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
   </ImportGroup>
-</Project>
+</Project>
\ No newline at end of file
index aa9a2523cc2b8c56797f8765b28be37b81d497f4..9d140cdfe3edf1693141ebeb9fbc228143bd75e2 100644 (file)
@@ -61,5 +61,6 @@
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="debugshim.h" />
+    <ClInclude Include="dbgshim.h" />
   </ItemGroup>
 </Project>
\ No newline at end of file
index b1daf52e54f58544a44209c7fed5e1ea87e1d99d..fae2869f381a920c80aeff8ae0c812111e3eebe2 100644 (file)
@@ -6,6 +6,7 @@ ResumeProcess
 CloseResumeHandle
 RegisterForRuntimeStartup
 RegisterForRuntimeStartupEx
+RegisterForRuntimeStartup3
 UnregisterForRuntimeStartup
 GetStartupNotificationEvent
 EnumerateCLRs
@@ -14,4 +15,5 @@ CreateVersionStringFromModule
 CreateDebuggingInterfaceFromVersion
 CreateDebuggingInterfaceFromVersionEx
 CreateDebuggingInterfaceFromVersion2
+CreateDebuggingInterfaceFromVersion3
 CLRCreateInstance
index 3dcfbcc25107851c31d068c2e8a3488358805151..964c69ac3c53b115eaecb20132399a91e2446497 100644 (file)
@@ -55,8 +55,7 @@ static bool IsTargetWindows(ICorDebugDataTarget* pDataTarget)
     CorDebugPlatform targetPlatform;
 
     HRESULT result = pDataTarget->GetPlatform(&targetPlatform);
-
-    if(FAILED(result))
+    if (FAILED(result))
     {
         _ASSERTE(!"Unexpected error");
         return false;
@@ -100,19 +99,13 @@ STDMETHODIMP CLRDebuggingImpl::OpenVirtualProcess(
     //PRECONDITION(CheckPointer(pDataTarget));
 
     HRESULT hr = S_OK;
-    ICorDebugDataTarget * pDt = NULL;
+    ClrInfo clrInfo;
+    SString dacModulePath;
+    SString dbiModulePath;
     HMODULE hDbi = NULL;
     HMODULE hDac = NULL;
-    LPWSTR pDacModulePath = NULL;
-    LPWSTR pDbiModulePath = NULL;
-    DWORD dbiTimestamp;
-    DWORD dbiSizeOfImage;
-    WCHAR dbiName[MAX_PATH_FNAME] = { 0 };
-    DWORD dacTimestamp;
-    DWORD dacSizeOfImage;
-    WCHAR dacName[MAX_PATH_FNAME] = { 0 };
+    ICorDebugDataTarget * pDt = NULL;
     CLR_DEBUGGING_VERSION version;
-    BOOL versionSupportedByCaller = FALSE;
 
     // argument checking
     if ((ppProcess != NULL || pFlags != NULL) && pLibraryProvider == NULL)
@@ -140,97 +133,43 @@ STDMETHODIMP CLRDebuggingImpl::OpenVirtualProcess(
         // 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);
+        hr = GetCLRInfo(pDt, moduleBaseAddress, &version, clrInfo);
     }
 
     // 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))
     {
-        ICLRDebuggingLibraryProvider2* pLibraryProvider2;
-        if (SUCCEEDED(pLibraryProvider->QueryInterface(__uuidof(ICLRDebuggingLibraryProvider2), (void**)&pLibraryProvider2)))
-        {
-            if (FAILED(pLibraryProvider2->ProvideLibrary2(dbiName, dbiTimestamp, dbiSizeOfImage, &pDbiModulePath)) ||
-                pDbiModulePath == NULL)
-            {
-                hr = CORDBG_E_LIBRARY_PROVIDER_ERROR;
-            }
+        hr = ProvideLibraries(clrInfo, pLibraryProvider, dbiModulePath, dacModulePath, &hDbi, &hDac);
 
-            if (SUCCEEDED(hr))
-            {
-                hDbi = LoadLibraryW(pDbiModulePath);
-                if (hDbi == NULL)
-                {
-                    hr = HRESULT_FROM_WIN32(GetLastError());
-                }
-            }
-
-            if (SUCCEEDED(hr))
+        // Need to load the DAC first because DBI references the PAL exports in the DAC
+        if (SUCCEEDED(hr) && hDac == NULL)
+        {
+            hDac = LoadLibraryW(dacModulePath);
+            if (hDac == NULL)
             {
-                // 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(pLibraryProvider2->ProvideLibrary2(dacName, dacTimestamp, dacSizeOfImage, &pDacModulePath)) ||
-                    pDacModulePath == NULL)
-                {
-                    hr = CORDBG_E_LIBRARY_PROVIDER_ERROR;
-                }
-
-                if (SUCCEEDED(hr))
-                {
-                    hDac = LoadLibraryW(pDacModulePath);
-                    if (hDac == NULL)
-                    {
-                        hr = HRESULT_FROM_WIN32(GetLastError());
-                    }
-                }
+                hr = HRESULT_FROM_WIN32(GetLastError());
             }
-
-            pLibraryProvider2->Release();
         }
-        else {
-            // Ask library provider for dbi
-            if (FAILED(pLibraryProvider->ProvideLibrary(dbiName, dbiTimestamp, dbiSizeOfImage, &hDbi)) ||
-                hDbi == NULL)
-            {
-                hr = CORDBG_E_LIBRARY_PROVIDER_ERROR;
-            }
 
-            if (SUCCEEDED(hr))
+        if (SUCCEEDED(hr) && hDbi == NULL)
+        {
+            hDbi = LoadLibraryW(dbiModulePath);
+            if (hDbi == NULL)
             {
-                // 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;
-                }
+                hr = HRESULT_FROM_WIN32(GetLastError());
             }
         }
 
         *ppProcess = NULL;
 
-        if (SUCCEEDED(hr) && pDacModulePath != NULL)
+        if (SUCCEEDED(hr) && !dacModulePath.IsEmpty())
         {
             // Get access to the latest OVP implementation and call it
             OpenVirtualProcessImpl2FnPtr ovpFn = (OpenVirtualProcessImpl2FnPtr)GetProcAddress(hDbi, "OpenVirtualProcessImpl2");
             if (ovpFn != NULL)
             {
-                hr = ovpFn(moduleBaseAddress, pDataTarget, pDacModulePath, pMaxDebuggerSupportedVersion, riidProcess, ppProcess, pFlags);
+                hr = ovpFn(moduleBaseAddress, pDataTarget, dacModulePath, pMaxDebuggerSupportedVersion, riidProcess, ppProcess, pFlags);
                 if (FAILED(hr))
                 {
                     _ASSERTE(ppProcess == NULL || *ppProcess == NULL);
@@ -246,7 +185,7 @@ STDMETHODIMP CLRDebuggingImpl::OpenVirtualProcess(
                 LoadLibraryWFnPtr loadLibraryWFn = (LoadLibraryWFnPtr)GetProcAddress(hDac, "LoadLibraryW");
                 if (loadLibraryWFn != NULL)
                 {
-                    hDac = loadLibraryWFn(pDacModulePath);
+                    hDac = loadLibraryWFn(dacModulePath);
                     if (hDac == NULL)
                     {
                         hr = E_HANDLE;
@@ -291,39 +230,239 @@ STDMETHODIMP CLRDebuggingImpl::OpenVirtualProcess(
         }
     }
 
-    //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)))
+    // 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));
     }
 
-    if (pDacModulePath != NULL)
+    // free the data target we QI'ed earlier
+    if (pDt != NULL)
     {
-#ifdef HOST_UNIX
-        free(pDacModulePath);
-#else
-        CoTaskMemFree(pDacModulePath);
-#endif
+        pDt->Release();
+    }
+
+    return hr;
+}
+
+// Call the library provider to get the DBI and DAC
+//
+// Arguments:
+//   clrInfo - the runtime info
+//   pLibraryProvider - a callback for locating DBI and DAC
+//   dbiModulePath - returns the DBI module path
+//   dacModulePath - returns the DAC module path
+HRESULT CLRDebuggingImpl::ProvideLibraries(
+    ClrInfo& clrInfo,
+    ICLRDebuggingLibraryProvider3* pLibraryProvider,
+    SString& dbiModulePath,
+    SString& dacModulePath)
+{
+    HMODULE hDbi = NULL;
+    HMODULE hDac = NULL;
+    HRESULT hr = CLRDebuggingImpl::ProvideLibraries(clrInfo, pLibraryProvider, dbiModulePath, dacModulePath, &hDbi, &hDac);
+    if (SUCCEEDED(hr))
+    {
+        // The dbgshim create DBI instance APIs don't support just ICLRDebuggingLibraryProvider which is what
+        // it means if the handles returned are not null. At least ICLRDebuggingLibraryProvider2 is needed and
+        // ICLRDebuggingLibraryProvider3 for Unix platforms.
+        if (hDbi != NULL || hDac != NULL)
+        {
+            hr = E_INVALIDARG;
+        }
+    }
+    return hr;
+}
+
+// Call the library provider to get the DBI and DAC
+//
+// Arguments:
+//   clrInfo - the runtime info
+//   pLibraryProvider - a callback for locating DBI and DAC
+//   dbiModulePath - returns the DBI module path
+//   dacModulePath - returns the DAC module path
+//   phDbi - returns the DBI module handle if old library provider
+//   phDac - returns the DAC module handle if old library provider
+HRESULT CLRDebuggingImpl::ProvideLibraries(
+    ClrInfo& clrInfo,
+    IUnknown* punk,
+    SString& dbiModulePath,
+    SString& dacModulePath,
+    HMODULE* phDbi,
+    HMODULE* phDac)
+{
+    ReleaseHolder<ICLRDebuggingLibraryProvider3> pLibraryProvider3;
+    ReleaseHolder<ICLRDebuggingLibraryProvider2> pLibraryProvider2;
+    ReleaseHolder<ICLRDebuggingLibraryProvider> pLibraryProvider;
+    LPWSTR pDbiModulePath = NULL;
+    LPWSTR pDacModulePath = NULL;
+    HRESULT hr = S_OK;
+
+    _ASSERTE(punk != NULL);
+    _ASSERTE(phDbi != NULL);
+    _ASSERTE(phDac != NULL);
+
+    // Validate the incoming index info
+    if (!clrInfo.IsValid())
+    {
+        hr = CORDBG_E_INCOMPATIBLE_PROTOCOL;
+        goto exit;
+    }
+
+    if (SUCCEEDED(punk->QueryInterface(__uuidof(ICLRDebuggingLibraryProvider3), (void**)&pLibraryProvider3)))
+    {
+        const WCHAR* wszRuntimeModulePath = !clrInfo.RuntimeModulePath.IsEmpty() ? clrInfo.RuntimeModulePath.GetUnicode() : NULL;
+        if (clrInfo.WindowsTarget)
+        {
+            // Ask library provider for DBI
+            if (FAILED(pLibraryProvider3->ProvideWindowsLibrary(
+                clrInfo.DbiName, 
+                wszRuntimeModulePath,
+                clrInfo.IndexType,
+                clrInfo.DbiTimeStamp,
+                clrInfo.DbiSizeOfImage,
+                &pDbiModulePath)) || pDbiModulePath == NULL)
+            {
+                hr = CORDBG_E_LIBRARY_PROVIDER_ERROR;
+                goto exit;
+            }
+            // Ask library provider for DAC
+            if (FAILED(pLibraryProvider3->ProvideWindowsLibrary(
+                clrInfo.DacName, 
+                wszRuntimeModulePath,
+                clrInfo.IndexType,
+                clrInfo.DacTimeStamp,
+                clrInfo.DacSizeOfImage,
+                &pDacModulePath)) || pDacModulePath == NULL)
+            {
+                hr = CORDBG_E_LIBRARY_PROVIDER_ERROR;
+                goto exit;
+            }
+        }
+        else 
+        {
+            BYTE* dbiBuildId = NULL;
+            ULONG dbiBuildIdSize = 0;
+            BYTE* dacBuildId = NULL;
+            ULONG dacBuildIdSize = 0;
+
+            // What kind of build id are we going to give the provider
+            switch (clrInfo.IndexType)
+            {
+                case LIBRARY_PROVIDER_INDEX_TYPE::Identity: 
+                    if (clrInfo.DbiBuildIdSize > 0)
+                    {
+                        dbiBuildId = clrInfo.DbiBuildId;
+                        dbiBuildIdSize = clrInfo.DbiBuildIdSize;
+                    }
+                    if (clrInfo.DacBuildIdSize > 0)
+                    {
+                        dacBuildId = clrInfo.DacBuildId;
+                        dacBuildIdSize = clrInfo.DacBuildIdSize;
+                    }
+                    break;
+                case LIBRARY_PROVIDER_INDEX_TYPE::Runtime: 
+                    if (clrInfo.RuntimeBuildIdSize > 0)
+                    {
+                        dbiBuildId = clrInfo.RuntimeBuildId;
+                        dbiBuildIdSize = clrInfo.RuntimeBuildIdSize;
+                        dacBuildId = clrInfo.RuntimeBuildId;
+                        dacBuildIdSize = clrInfo.RuntimeBuildIdSize;
+                    }
+                    break;
+                default:
+                    hr = CORDBG_E_LIBRARY_PROVIDER_ERROR;
+                    goto exit;
+            }
+            // Ask library provider for DBI
+            if (FAILED(pLibraryProvider3->ProvideUnixLibrary(
+                clrInfo.DbiName, 
+                wszRuntimeModulePath,
+                clrInfo.IndexType,
+                dbiBuildId, 
+                dbiBuildIdSize,
+                &pDbiModulePath)) || pDbiModulePath == NULL)
+            {
+                hr = CORDBG_E_LIBRARY_PROVIDER_ERROR;
+                goto exit;
+            }
+            // Ask library provider for DAC
+            if (FAILED(pLibraryProvider3->ProvideUnixLibrary(
+                clrInfo.DacName, 
+                wszRuntimeModulePath,
+                clrInfo.IndexType,
+                dacBuildId,
+                dacBuildIdSize,
+                &pDacModulePath)) || pDacModulePath == NULL)
+            {
+                hr = CORDBG_E_LIBRARY_PROVIDER_ERROR;
+                goto exit;
+            }
+        }
+    }
+    else if (SUCCEEDED(punk->QueryInterface(__uuidof(ICLRDebuggingLibraryProvider2), (void**)&pLibraryProvider2)))
+    {
+        // Ask library provider for DBI
+        if (FAILED(pLibraryProvider2->ProvideLibrary2(clrInfo.DbiName, clrInfo.DbiTimeStamp, clrInfo.DbiSizeOfImage, &pDbiModulePath)) || pDbiModulePath == NULL)
+        {
+            hr = CORDBG_E_LIBRARY_PROVIDER_ERROR;
+            goto exit;
+        }
+
+        // Adjust the timestamp and size of image if this DAC is a known buggy version and needs to be retargeted
+        RetargetDacIfNeeded(&clrInfo.DacTimeStamp, &clrInfo.DacSizeOfImage);
+
+        // Ask library provider for DAC
+        if (FAILED(pLibraryProvider2->ProvideLibrary2(clrInfo.DacName, clrInfo.DacTimeStamp, clrInfo.DacSizeOfImage, &pDacModulePath)) || pDacModulePath == NULL)
+        {
+            hr = CORDBG_E_LIBRARY_PROVIDER_ERROR;
+            goto exit;
+        }
+    }
+    else if (SUCCEEDED(punk->QueryInterface(__uuidof(ICLRDebuggingLibraryProvider), (void**)&pLibraryProvider)))
+    {
+        // Ask library provider for DBI
+        if (FAILED(pLibraryProvider->ProvideLibrary(clrInfo.DbiName, clrInfo.DbiTimeStamp, clrInfo.DbiSizeOfImage, phDbi)) || *phDbi == NULL)
+        {
+            hr = CORDBG_E_LIBRARY_PROVIDER_ERROR;
+            goto exit;
+        }
+
+        // Adjust the timestamp and size of image if this DAC is a known buggy version and needs to be retargeted
+        RetargetDacIfNeeded(&clrInfo.DacTimeStamp, &clrInfo.DacSizeOfImage);
+
+        // ask library provider for DAC
+        if (FAILED(pLibraryProvider->ProvideLibrary(clrInfo.DacName, clrInfo.DacTimeStamp, clrInfo.DacSizeOfImage, phDac)) || *phDac == NULL)
+        {
+            hr = CORDBG_E_LIBRARY_PROVIDER_ERROR;
+            goto exit;
+        }
+    }
+    else 
+    {
+        hr = CORDBG_E_LIBRARY_PROVIDER_ERROR;
+        goto exit;
     }
 
+exit:
     if (pDbiModulePath != NULL)
     {
+        dbiModulePath.Set(pDbiModulePath);
 #ifdef HOST_UNIX
         free(pDbiModulePath);
 #else
         CoTaskMemFree(pDbiModulePath);
 #endif
     }
-
-    // free the data target we QI'ed earlier
-    if (pDt != NULL)
+    if (pDacModulePath != NULL)
     {
-        pDt->Release();
+        dacModulePath.Set(pDacModulePath);
+#ifdef HOST_UNIX
+        free(pDacModulePath);
+#else
+        CoTaskMemFree(pDacModulePath);
+#endif
     }
-
     return hr;
 }
 
@@ -395,48 +534,24 @@ VOID CLRDebuggingImpl::RetargetDacIfNeeded(DWORD* pdwTimeStamp,
 
 #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,
+//   clrInfo - various info about the runtime
+HRESULT CLRDebuggingImpl::GetCLRInfo(ICorDebugDataTarget * pDataTarget,
                                      ULONG64 moduleBaseAddress,
                                      CLR_DEBUGGING_VERSION* pVersion,
-                                     DWORD* pdwDbiTimeStamp,
-                                     DWORD* pdwDbiSizeOfImage,
-                                     _Inout_updates_z_(dwDbiNameCharCount) WCHAR* pDbiName,
-                                     DWORD  dwDbiNameCharCount,
-                                     DWORD* pdwDacTimeStamp,
-                                     DWORD* pdwDacSizeOfImage,
-                                     _Inout_updates_z_(dwDacNameCharCount) WCHAR* pDacName,
-                                     DWORD  dwDacNameCharCount)
+                                     ClrInfo& clrInfo)
 {
+    memset(pVersion, 0, sizeof(CLR_DEBUGGING_VERSION));
 #ifdef HOST_WINDOWS
-    if(IsTargetWindows(pDataTarget))
+    if (IsTargetWindows(pDataTarget))
     {
+        clrInfo.WindowsTarget = TRUE;
+
         WORD imageFileMachine = 0;
         DWORD resourceSectionRVA = 0;
         HRESULT hr = GetMachineAndResourceSectionRVA(pDataTarget, moduleBaseAddress, &imageFileMachine, &resourceSectionRVA);
@@ -444,15 +559,19 @@ HRESULT CLRDebuggingImpl::GetCLRInfo(ICorDebugDataTarget* pDataTarget,
         // We want the version resource which has type = RT_VERSION = 16, name = 1, language = 0x409
         DWORD versionResourceRVA = 0;
         DWORD versionResourceSize = 0;
-        if(SUCCEEDED(hr))
+        if (SUCCEEDED(hr))
         {
-            hr = GetResourceRvaFromResourceSectionRva(pDataTarget, moduleBaseAddress, resourceSectionRVA, 16, 1, 0x409,
-                     &versionResourceRVA, &versionResourceSize);
+            hr = GetResourceRvaFromResourceSectionRva(pDataTarget, moduleBaseAddress, resourceSectionRVA, 16, 1, 0x409, &versionResourceRVA, &versionResourceSize);
+            if (FAILED(hr))
+            {
+                // The single-file apps are language "neutral" (0)
+                hr = GetResourceRvaFromResourceSectionRva(pDataTarget, moduleBaseAddress, resourceSectionRVA, 16, 1, 0, &versionResourceRVA, &versionResourceSize);
+            }
         }
 
         // At last we get our version info
         VS_FIXEDFILEINFO fixedFileInfo = {0};
-        if(SUCCEEDED(hr))
+        if (SUCCEEDED(hr))
         {
             // The version resource has 3 words, then the unicode string "VS_VERSION_INFO"
             // (16 WCHARS including the null terminator)
@@ -461,14 +580,14 @@ HRESULT CLRDebuggingImpl::GetCLRInfo(ICorDebugDataTarget* pDataTarget,
             hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + fixedFileInfoRVA, (BYTE*)&fixedFileInfo, sizeof(fixedFileInfo));
         }
 
-        //Verify the signature on the version resource
-        if(SUCCEEDED(hr) && fixedFileInfo.dwSignature != PE_FIXEDFILEINFO_SIGNATURE)
+        // 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))
+        if (SUCCEEDED(hr))
         {
             pVersion->wMajor = (WORD) (fixedFileInfo.dwProductVersionMS >> 16);
             pVersion->wMinor = (WORD) (fixedFileInfo.dwProductVersionMS & 0xFFFF);
@@ -483,7 +602,7 @@ HRESULT CLRDebuggingImpl::GetCLRInfo(ICorDebugDataTarget* pDataTarget,
         DWORD debugResourceRVA = 0;
         DWORD debugResourceSize = 0;
         BOOL useCrossPlatformNaming = FALSE;
-        if(SUCCEEDED(hr))
+        if (SUCCEEDED(hr))
         {
             // the initial state is that we haven't found a proper resource
             HRESULT hrGetResource = E_FAIL;
@@ -521,11 +640,9 @@ HRESULT CLRDebuggingImpl::GetCLRInfo(ICorDebugDataTarget* pDataTarget,
             const WCHAR * resourceName = W("CLRDEBUGINFOCORESYSARM");
     #endif
 
-            hrGetResource = GetResourceRvaFromResourceSectionRvaByName(pDataTarget, moduleBaseAddress, resourceSectionRVA, 10, resourceName, 0,
-                     &debugResourceRVA, &debugResourceSize);
+            hrGetResource = GetResourceRvaFromResourceSectionRvaByName(pDataTarget, moduleBaseAddress, resourceSectionRVA, 10, resourceName, 0, &debugResourceRVA, &debugResourceSize);
             useCrossPlatformNaming = SUCCEEDED(hrGetResource);
 
-
     #if defined(HOST_WINDOWS) && (defined(HOST_X86) || defined(HOST_AMD64) || defined(HOST_ARM))
       #if defined(HOST_X86)
         #define _HOST_MACHINE_TYPE IMAGE_FILE_MACHINE_I386
@@ -536,70 +653,68 @@ HRESULT CLRDebuggingImpl::GetCLRInfo(ICorDebugDataTarget* pDataTarget,
       #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))
+            if (FAILED(hrGetResource) && (imageFileMachine == _HOST_MACHINE_TYPE))
             {
-                hrGetResource = GetResourceRvaFromResourceSectionRvaByName(pDataTarget, moduleBaseAddress, resourceSectionRVA, 10, W("CLRDEBUGINFO"), 0,
-                     &debugResourceRVA, &debugResourceSize);
+                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))
+        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))
+        if (SUCCEEDED(hr))
         {
             hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + debugResourceRVA, (BYTE*)&debugResource, sizeof(debugResource));
         }
-        if(SUCCEEDED(hr) && (debugResource.dwVersion != 0))
+        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) ))
+        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)
+        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"));
+            FormatLongDacModuleName(clrInfo.DacName, MAX_PATH_FNAME, imageFileMachine, &fixedFileInfo);
+            swprintf_s(clrInfo.DbiName, MAX_PATH_FNAME, 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);
+                swprintf_s(clrInfo.DacName, MAX_PATH_FNAME, 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);
+                swprintf_s(clrInfo.DacName, MAX_PATH_FNAME, W("%s.dll"), CORECLR_DAC_MODULE_NAME_W);
+            swprintf_s(clrInfo.DbiName, MAX_PATH_FNAME, W("%s.dll"), MAIN_DBI_MODULE_NAME_W);
         }
 
-        if(SUCCEEDED(hr))
+        if (SUCCEEDED(hr))
         {
-            *pdwDbiTimeStamp = debugResource.dwDbiTimeStamp;
-            *pdwDbiSizeOfImage = debugResource.dwDbiSizeOfImage;
-            *pdwDacTimeStamp = debugResource.dwDacTimeStamp;
-            *pdwDacSizeOfImage = debugResource.dwDacSizeOfImage;
+            clrInfo.IndexType = LIBRARY_PROVIDER_INDEX_TYPE::Identity; 
+            clrInfo.DbiTimeStamp = debugResource.dwDbiTimeStamp;
+            clrInfo.DbiSizeOfImage = debugResource.dwDbiSizeOfImage;
+            clrInfo.DacTimeStamp = debugResource.dwDacTimeStamp;
+            clrInfo.DacSizeOfImage = debugResource.dwDacSizeOfImage;
         }
 
         // any failure should be interpreted as this module not being a CLR
-        if(FAILED(hr))
+        if (FAILED(hr))
         {
             return CORDBG_E_NOT_CLR;
         }
@@ -611,18 +726,44 @@ HRESULT CLRDebuggingImpl::GetCLRInfo(ICorDebugDataTarget* pDataTarget,
     else
 #endif // !HOST_WINDOWS
     {
-        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));
+        clrInfo.WindowsTarget = FALSE;
 
-        pVersion->wMajor = 0;
-        pVersion->wMinor = 0;
-        pVersion->wBuild = 0;
-        pVersion->wRevision = 0;
+        //
+        // Check if it is a single-file app
+        //
+        uint64_t symbolAddress;
+        if (TryGetSymbol(pDataTarget, moduleBaseAddress, RUNTIME_INFO_SIGNATURE, &symbolAddress))
+        {
+            RuntimeInfo runtimeInfo;
+            ULONG32 bytesRead;
+            if (SUCCEEDED(pDataTarget->ReadVirtual(symbolAddress, (BYTE*)&runtimeInfo, sizeof(RuntimeInfo), &bytesRead)))
+            {
+                if (strcmp(runtimeInfo.Signature, RUNTIME_INFO_SIGNATURE) == 0)
+                {
+                    // This is a single-file app
+                    clrInfo.IndexType = LIBRARY_PROVIDER_INDEX_TYPE::Identity; 
 
-        *pdwDbiTimeStamp = 0;
-        *pdwDbiSizeOfImage = 0;
-        *pdwDacTimeStamp = 0;
-        *pdwDacSizeOfImage = 0;
+                    // The first byte is the number of bytes in the index
+                    clrInfo.DbiBuildIdSize = runtimeInfo.DbiModuleIndex[0];
+                    memcpy_s(&clrInfo.DbiBuildId, sizeof(clrInfo.DbiBuildId), &(runtimeInfo.DbiModuleIndex[1]), clrInfo.DbiBuildIdSize);
+
+                    clrInfo.DacBuildIdSize = runtimeInfo.DacModuleIndex[0];
+                    memcpy_s(&clrInfo.DacBuildId, sizeof(clrInfo.DacBuildId), &(runtimeInfo.DacModuleIndex[1]), clrInfo.DacBuildIdSize);
+                }
+            }
+        }
+
+        //
+        // If it wasn't a single-file app, then fallback to getting the runtime module's index information
+        //
+        if (!clrInfo.IsValid())
+        {
+            if (TryGetBuildId(pDataTarget, moduleBaseAddress, clrInfo.RuntimeBuildId, MAX_BUILDID_SIZE, &clrInfo.RuntimeBuildIdSize)) 
+            {
+                // This is normal non-single-file app
+                clrInfo.IndexType = LIBRARY_PROVIDER_INDEX_TYPE::Runtime; 
+            }
+        }
 
         return S_OK;
     }
@@ -741,8 +882,6 @@ STDMETHODIMP CLRDebuggingImpl::CanUnloadNow(HMODULE hModule)
     return hr;
 }
 
-
-
 STDMETHODIMP CLRDebuggingImpl::QueryInterface(REFIID riid, void **ppvObject)
 {
     HRESULT hr = S_OK;
index 93df463867feb5462b814f7d5aeba2d78963dce3..2d5d556b022a3214f4a75dff27edafa5b5a00ba3 100644 (file)
 
 #include "cor.h"
 #include "cordebug.h"
+#include "sstring.h"
 #include <wchar.h>
 #include <metahost.h>
+#include "runtimeinfo.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")
 
+#define MAX_BUILDID_SIZE 24
+
+// 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;
+};
+
+struct ClrInfo
+{
+    BOOL WindowsTarget;
+    LIBRARY_PROVIDER_INDEX_TYPE IndexType;
+
+    SString RuntimeModulePath;
+    BYTE RuntimeBuildId[MAX_BUILDID_SIZE];
+    ULONG RuntimeBuildIdSize;
+
+    DWORD DbiTimeStamp;
+    DWORD DbiSizeOfImage;
+    BYTE DbiBuildId[MAX_BUILDID_SIZE];
+    ULONG DbiBuildIdSize;
+    WCHAR DbiName[MAX_PATH_FNAME];
+
+    DWORD DacTimeStamp;
+    DWORD DacSizeOfImage;
+    BYTE DacBuildId[MAX_BUILDID_SIZE];
+    ULONG DacBuildIdSize;
+    WCHAR DacName[MAX_PATH_FNAME];
+
+    ClrInfo()
+    {
+#ifdef HOST_UNIX 
+        WindowsTarget = FALSE;
+#else
+        WindowsTarget = TRUE;
+#endif
+        IndexType = LIBRARY_PROVIDER_INDEX_TYPE::Unknown; 
+
+        memset(&RuntimeBuildId, 0, sizeof(RuntimeBuildId));
+        RuntimeBuildIdSize = 0;
+
+        DbiTimeStamp = 0;
+        DbiSizeOfImage = 0;
+        memset(&DbiBuildId, 0, sizeof(DbiBuildId));
+        DbiBuildIdSize = 0;
+
+        DacTimeStamp = 0;
+        DacSizeOfImage = 0;;
+        memset(&DacBuildId, 0, sizeof(DacBuildId));
+        DacBuildIdSize = 0;
+
+        swprintf_s(DbiName, MAX_PATH_FNAME, W("%s"), MAKEDLLNAME_W(MAIN_DBI_MODULE_NAME_W));
+        swprintf_s(DacName, MAX_PATH_FNAME, W("%s"), MAKEDLLNAME_W(CORECLR_DAC_MODULE_NAME_W));
+    }
+
+    bool IsValid()
+    {
+        if (IndexType == LIBRARY_PROVIDER_INDEX_TYPE::Identity)
+        {
+            if (WindowsTarget)
+            {
+                return DbiTimeStamp != 0 && DbiSizeOfImage != 0 && DacTimeStamp != 0 && DacSizeOfImage;
+            }
+            else 
+            {
+                return DbiBuildIdSize > 0 && DacBuildIdSize > 0;
+            }
+        }
+        else if (IndexType == LIBRARY_PROVIDER_INDEX_TYPE::Runtime)
+        {
+            // The runtime index info should never be needed or provided on Windows
+            if (!WindowsTarget)
+            {
+                return RuntimeBuildIdSize > 0;
+            }
+        }
+        return false;
+    }
+};
+
+extern "C" bool TryGetSymbol(ICorDebugDataTarget* dataTarget, uint64_t baseAddress, const char* symbolName, uint64_t* symbolAddress);
+extern "C" bool TryGetBuildId(ICorDebugDataTarget* dataTarget, uint64_t baseAddress, BYTE* buffer, ULONG bufferSize, PULONG pBuildIdSize);
+#ifdef TARGET_UNIX
+extern "C" bool TryReadSymbolFromFile(const WCHAR* modulePath, const char* symbolName, BYTE* buffer, ULONG32 size);
+extern "C" bool TryGetBuildIdFromFile(const WCHAR* modulePath, BYTE* buffer, ULONG bufferSize, PULONG pBuildSize);
+#endif
+
 // forward declaration
 struct ICorDebugDataTarget;
 
@@ -47,10 +141,8 @@ public:
 
     STDMETHOD(CanUnloadNow(HMODULE hModule));
 
-       //IUnknown methods:
-       STDMETHOD(QueryInterface(
-                REFIID riid,
-                void **ppvObject));
+       // IUnknown methods:
+       STDMETHOD(QueryInterface(REFIID riid, void **ppvObject));
 
        // Standard AddRef implementation
        STDMETHOD_(ULONG, AddRef());
@@ -58,23 +150,26 @@ public:
        // Standard Release implementation.
        STDMETHOD_(ULONG, Release());
 
-
+    static HRESULT ProvideLibraries(ClrInfo& clrInfo,
+                                    ICLRDebuggingLibraryProvider3* pLibraryProvider,
+                                    SString& dbiModulePath,
+                                    SString& dacModulePath);
 
 private:
-    VOID RetargetDacIfNeeded(DWORD* pdwTimeStamp,
-                             DWORD* pdwSizeOfImage);
+    static HRESULT ProvideLibraries(ClrInfo& clrInfo,
+                                    IUnknown* pLibraryProvider,
+                                    SString& dbiModulePath,
+                                    SString& dacModulePath,
+                                    HMODULE* phDbi,
+                                    HMODULE* phDac);
+
+    static VOID RetargetDacIfNeeded(DWORD* pdwTimeStamp,
+                                    DWORD* pdwSizeOfImage);
 
     HRESULT GetCLRInfo(ICorDebugDataTarget * pDataTarget,
                        ULONG64 moduleBaseAddress,
-                       CLR_DEBUGGING_VERSION * pVersion,
-                       DWORD * pdwDbiTimeStamp,
-                       DWORD * pdwDbiSizeOfImage,
-                       _Inout_updates_z_(dwDbiNameCharCount) WCHAR * pDbiName,
-                       DWORD   dwDbiNameCharCount,
-                       DWORD * pdwDacTimeStamp,
-                       DWORD * pdwDacSizeOfImage,
-                       _Inout_updates_z_(dwDacNameCharCount) WCHAR * pDacName,
-                       DWORD   dwDacNameCharCount);
+                       CLR_DEBUGGING_VERSION* pVersion,
+                       ClrInfo& clrInfo);
 
     HRESULT FormatLongDacModuleName(_Inout_updates_z_(cchBuffer) WCHAR * pBuffer,
                                     DWORD cchBuffer,
index b11cea1d2227e4284c8444ad2c1ca8c95151946c..66b0091dd920999ca6b209bd29183b0600d59d8b 100644 (file)
@@ -25,4 +25,10 @@ if(NOT DEFINED CLR_CMAKE_HOST_OSX)
     )
 endif(NOT DEFINED CLR_CMAKE_HOST_OSX)
 
+if(CLR_CMAKE_TARGET_OSX)
+    list(APPEND DBGUTIL_SOURCES
+        machoreader.cpp
+    )
+endif(CLR_CMAKE_TARGET_OSX)
+
 add_library(dbgutil STATIC ${DBGUTIL_SOURCES})
index fc9e9bb26f604980fdeb3ab0e12dde42150f9590..d2eb25746b0b7bb604561d56cbe1ff8941d1136f 100644 (file)
   <ItemGroup>
     <ClCompile Include="dbgutil.cpp" />
     <ClCompile Include="elfreader.cpp" />
+    <ClCompile Include="machoreader.cpp" />
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="elfreader.h" />
+    <ClInclude Include="machoreader.h" />
   </ItemGroup>
   <PropertyGroup Label="Globals">
     <ProjectGuid>{A9A7C879-C320-3327-BB84-16E1322E17AE}</ProjectGuid>
@@ -86,7 +88,7 @@
   </PropertyGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <ClCompile>
-      <AdditionalIncludeDirectories>..\..\SOS\dbgutil;..\..\pal\prebuilt\inc;..\..\inc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>$(RepoRoot)artifacts\obj;$(RepoRoot)src\shared;$(RepoRoot)src\shared\inc;$(RepoRoot)src\shared\pal\prebuilt\inc;$(RepoRoot)src\shared\pal\inc;$(RepoRoot)src\shared\pal\inc\rt;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <AdditionalOptions>%(AdditionalOptions) /d2Zi+ /Zm200 /ZH:SHA_256 /source-charset:utf-8 /homeparams</AdditionalOptions>
       <AssemblerListingLocation>Debug/</AssemblerListingLocation>
       <BufferSecurityCheck>true</BufferSecurityCheck>
       <WarningLevel>Level3</WarningLevel>
       <PreprocessorDefinitions>DEBUG;_DEBUG;_DBG;URTBLDENV_FRIENDLY=Checked;BUILDENV_CHECKED=1;_AMD64_;_WIN64;AMD64;BIT64=1;_TARGET_64BIT_=1;_TARGET_AMD64_=1;DBG_TARGET_64BIT=1;DBG_TARGET_AMD64=1;DBG_TARGET_WIN64=1;WIN32;_WIN32;WINVER=0x0602;_WIN32_WINNT=0x0602;WIN32_LEAN_AND_MEAN=1;_CRT_SECURE_NO_WARNINGS;FEATURE_COMINTEROP;FEATURE_HIJACK;_SECURE_SCL=0;CMAKE_INTDIR="Debug";%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <ObjectFileName>$(IntDir)</ObjectFileName>
+      <BrowseInformation>true</BrowseInformation>
     </ClCompile>
     <ResourceCompile>
       <PreprocessorDefinitions>WIN32;_DEBUG;DEBUG;_DBG;URTBLDENV_FRIENDLY=Checked;BUILDENV_CHECKED=1;_AMD64_;_WIN64;AMD64;BIT64=1;_TARGET_64BIT_=1;_TARGET_AMD64_=1;DBG_TARGET_64BIT=1;DBG_TARGET_AMD64=1;DBG_TARGET_WIN64=1;_WIN32;WINVER=0x0602;_WIN32_WINNT=0x0602;WIN32_LEAN_AND_MEAN=1;_CRT_SECURE_NO_WARNINGS;;FEATURE_COMINTEROP;FEATURE_HIJACK;_SECURE_SCL=0;CMAKE_INTDIR=\"Debug\";%(PreprocessorDefinitions)</PreprocessorDefinitions>
     <Lib>
       <AdditionalOptions>%(AdditionalOptions) /machine:x64 /IGNORE:4221</AdditionalOptions>
     </Lib>
+    <Bscmake>
+      <PreserveSbr>true</PreserveSbr>
+    </Bscmake>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Checked|x64'">
     <ClCompile>
   <ImportGroup Label="ExtensionTargets">
     <Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
   </ImportGroup>
-</Project>
+</Project>
\ No newline at end of file
index 423c15b7bcb468942e8d91219cc3f846736c1e5a..0848b3f3fde28a4674f09d46dbfdb275f25037f3 100644 (file)
@@ -2,10 +2,13 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 #include <windows.h>
+#include <clrdata.h>
+#include <cor.h>
+#include <cordebug.h>
 #define __STDC_FORMAT_MACROS
 #include <inttypes.h>
-#include <limits.h>
 #include "elfreader.h"
+#include "arrayholder.h"
 
 #define Elf_Ehdr   ElfW(Ehdr)
 #define Elf_Phdr   ElfW(Phdr)
 static const char ElfMagic[] = { 0x7f, 'E', 'L', 'F', '\0' };
 #endif
 
-extern bool ElfReaderReadMemory(void* address, void* buffer, size_t size);
+#ifdef HOST_UNIX
 
-class ElfReaderExport: public ElfReader
+class ElfReaderFromFile : public ElfReader
 {
+private:
+    struct ProgramHeader
+    {
+        uint64_t Start;
+        uint64_t End;
+        uint64_t FileOffset;
+    };
+    PAL_FILE* m_file;
+    std::vector<ProgramHeader> m_programHeaders;
+
 public:
-    ElfReaderExport()
+    ElfReaderFromFile() : ElfReader(true),
+        m_file(NULL)
+    {
+    }
+
+    virtual ~ElfReaderFromFile()
+    {
+        if (m_file != NULL)
+        {
+            PAL_fclose(m_file);
+            m_file = NULL;
+        }
+    }
+
+    bool OpenFile(const WCHAR* modulePath)
+    {
+        _ASSERTE(m_file == NULL);
+        m_file = _wfopen(modulePath, W("rb"));
+        return m_file != NULL;
+    }
+
+    uint64_t GetFileOffset(uint64_t address)
+    {
+        for (const ProgramHeader& header : m_programHeaders)
+        {
+            if (address >= header.Start && address < header.End)
+            {
+                return address - header.Start + header.FileOffset;
+            }
+        }
+        return 0;
+    }
+
+    virtual void VisitProgramHeader(uint64_t loadbias, uint64_t baseAddress, ElfW(Phdr)* phdr)
+    {
+        switch (phdr->p_type)
+        {
+        case PT_LOAD:
+            ProgramHeader header;
+            header.Start = loadbias + phdr->p_vaddr;
+            header.End = loadbias + phdr->p_vaddr + phdr->p_memsz;
+            header.FileOffset = phdr->p_offset;
+            m_programHeaders.push_back(header);
+            break;
+        }
+    }
+
+    virtual bool ReadMemory(void* address, void* buffer, size_t size)
+    {
+        if (m_file == NULL)
+        {
+            return false;
+        }
+        if (PAL_fseek(m_file, (LONG)address, SEEK_SET) != 0)
+        {
+            return false;
+        }
+        size_t read = PAL_fread(buffer, 1, size, m_file);
+        return read > 0;
+    }
+};
+
+//
+// Entry point to get an export symbol from a module file
+//
+extern "C" bool
+TryReadSymbolFromFile(const WCHAR* modulePath, const char* symbolName, BYTE* buffer, ULONG32 size)
+{
+    ElfReaderFromFile reader;
+    if (reader.OpenFile(modulePath))
     {
+        if (reader.PopulateForSymbolLookup(0))
+        {
+            uint64_t symbolOffset;
+            if (reader.TryLookupSymbol(symbolName, &symbolOffset))
+            {
+                symbolOffset = reader.GetFileOffset(symbolOffset);
+                if (symbolOffset != 0)
+                {
+                    return reader.ReadMemory((void*)symbolOffset, buffer, size);
+                }
+            }
+        }
+    }
+    return false;
+}
+
+//
+// Entry point to get the ELF file's build id
+//
+extern "C" bool
+TryGetBuildIdFromFile(const WCHAR* modulePath, BYTE* buffer, ULONG bufferSize, PULONG pBuildSize)
+{
+    ElfReaderFromFile reader;
+    if (reader.OpenFile(modulePath))
+    {
+        if (reader.EnumerateProgramHeaders(0, nullptr, nullptr))
+        {
+            return reader.GetBuildId(buffer, bufferSize, pBuildSize);
+        }
+    }
+    return false;
+}
+
+#endif // HOST_UNIX
+
+typedef bool (*ReadMemoryCallback)(void* address, void* buffer, size_t size);
+
+class ElfReaderWithCallback : public ElfReader
+{
+private:
+    ReadMemoryCallback m_readMemory;
+
+public:
+    ElfReaderWithCallback(ReadMemoryCallback readMemory) : ElfReader(false),
+        m_readMemory(readMemory)
+    {
+    }
+
+    virtual ~ElfReaderWithCallback()
+    {
+    }
+
+private:
+    virtual bool ReadMemory(void* address, void* buffer, size_t size)
+    {
+        return m_readMemory(address, buffer, size);
+    }
+};
+
+//
+// Entry point to get an export symbol
+//
+extern "C" bool
+TryGetSymbolWithCallback(ReadMemoryCallback readMemory, uint64_t baseAddress, const char* symbolName, uint64_t* symbolAddress)
+{
+    ElfReaderWithCallback reader(readMemory);
+    if (reader.PopulateForSymbolLookup(baseAddress))
+    {
+        uint64_t symbolOffset;
+        if (reader.TryLookupSymbol(symbolName, &symbolOffset))
+        {
+            *symbolAddress = baseAddress + symbolOffset;
+            return true;
+        }
+    }
+    *symbolAddress = 0;
+    return false;
+}
+
+class ElfReaderExport : public ElfReader
+{
+private:
+    ICorDebugDataTarget* m_dataTarget;
+
+public:
+    ElfReaderExport(ICorDebugDataTarget* dataTarget) : ElfReader(false),
+        m_dataTarget(dataTarget)
+    {
+        dataTarget->AddRef();
     }
 
     virtual ~ElfReaderExport()
     {
+        m_dataTarget->Release();
     }
 
 private:
     virtual bool ReadMemory(void* address, void* buffer, size_t size)
     {
-        return ElfReaderReadMemory(address, buffer, size);
+        uint32_t read = 0;
+        return SUCCEEDED(m_dataTarget->ReadVirtual(reinterpret_cast<CLRDATA_ADDRESS>(address), reinterpret_cast<PBYTE>(buffer), (uint32_t)size, &read));
     }
 };
 
@@ -56,13 +229,13 @@ private:
 // Main entry point to get an export symbol
 //
 extern "C" bool
-TryGetSymbol(uint64_t baseAddress, const char* symbolName, uint64_t* symbolAddress)
+TryGetSymbol(ICorDebugDataTarget* dataTarget, uint64_t baseAddress, const char* symbolName, uint64_t* symbolAddress)
 {
-    ElfReaderExport elfreader;
-    if (elfreader.PopulateForSymbolLookup(baseAddress))
+    ElfReaderExport reader(dataTarget);
+    if (reader.PopulateForSymbolLookup(baseAddress))
     {
         uint64_t symbolOffset;
-        if (elfreader.TryLookupSymbol(symbolName, &symbolOffset))
+        if (reader.TryLookupSymbol(symbolName, &symbolOffset))
         {
             *symbolAddress = baseAddress + symbolOffset;
             return true;
@@ -72,17 +245,35 @@ TryGetSymbol(uint64_t baseAddress, const char* symbolName, uint64_t* symbolAddre
     return false;
 }
 
+
+//
+// Get the build id of the module from a data target
+//
+extern "C" bool
+TryGetBuildId(ICorDebugDataTarget* dataTarget, uint64_t baseAddress, BYTE* buffer, ULONG bufferSize, PULONG pBuildSize)
+{
+    ElfReaderExport reader(dataTarget);
+    if (reader.EnumerateProgramHeaders(baseAddress, nullptr, nullptr))
+    {
+        return reader.GetBuildId(buffer, bufferSize, pBuildSize);
+    }
+    return false;
+}
+
 //
 // ELF reader constructor/destructor
 //
 
-ElfReader::ElfReader() :
+ElfReader::ElfReader(bool isFileLayout) :
+    m_isFileLayout(isFileLayout),
     m_gnuHashTableAddr(nullptr),
     m_stringTableAddr(nullptr),
     m_stringTableSize(0),
     m_symbolTableAddr(nullptr),
     m_buckets(nullptr),
-    m_chainsAddress(nullptr)
+    m_chainsAddress(nullptr),
+    m_noteStart(0),
+    m_noteEnd(0)
 {
     memset(&m_hashTable, 0, sizeof(m_hashTable));
 }
@@ -186,7 +377,7 @@ ElfReader::TryLookupSymbol(std::string symbolName, uint64_t* symbolOffset)
                     if (symbolName.compare(possibleName) == 0)
                     {
                         *symbolOffset = symbol.st_value;
-                        Trace("TryLookupSymbol found '%s' at offset %" PRIxA "\n", symbolName.c_str(), *symbolOffset);
+                        Trace("TryLookupSymbol found '%s' at offset %" PRIxA " in %d\n", symbolName.c_str(), *symbolOffset, symbol.st_shndx);
                         return true;
                     }
                 }
@@ -306,6 +497,50 @@ ElfReader::GetStringAtIndex(int index, std::string& result)
     return true;
 }
 
+size_t Align4(size_t x) { return (x + 3) & ~3; }
+
+bool 
+ElfReader::GetBuildId(BYTE* buffer, ULONG bufferSize, PULONG pBuildSize)
+{
+    _ASSERTE(pBuildSize != nullptr);
+
+    if (m_noteStart != 0)
+    {
+        _ASSERTE(m_noteEnd != 0);
+        uint64_t address = m_noteStart;
+        while (address < m_noteEnd)
+        {
+            Elf_Nhdr nhdr;
+            if (!ReadMemory((PVOID)address, &nhdr, sizeof(nhdr)))
+            {
+                return false;
+            }
+            size_t nhdrSize = sizeof(Elf_Nhdr) + Align4(nhdr.n_namesz) + Align4(nhdr.n_descsz);
+            Trace("GetBuildId: type %d size %08x\n", nhdr.n_type, nhdrSize);
+            if (nhdr.n_type == NT_GNU_BUILD_ID)
+            {
+                ArrayHolder<BYTE> nhdrBuffer = new BYTE[nhdrSize];
+                if (!ReadMemory((PVOID)address, nhdrBuffer, nhdrSize))
+                {
+                    return false;
+                }
+                const char* name = (const char*)(nhdrBuffer.GetPtr() + sizeof(Elf_Nhdr));
+                Trace("GetBuildId: name %s\n", name);
+                if (strncmp(name, ELF_NOTE_GNU, Align4(nhdr.n_namesz)) == 0)
+                {
+                    *pBuildSize = nhdr.n_descsz;
+                    memcpy_s(buffer, bufferSize, nhdrBuffer.GetPtr() + sizeof(Elf_Nhdr) + Align4(nhdr.n_namesz), nhdr.n_descsz);
+
+                    Trace("GetBuildId: found id size %d\n", *pBuildSize);
+                    return true;
+                }
+            }
+            address += nhdrSize;
+        }
+    }
+    return false;
+}
+
 #ifdef HOST_UNIX
 
 //
@@ -483,9 +718,22 @@ ElfReader::EnumerateProgramHeaders(Elf_Phdr* phdrAddr, int phnum, uint64_t baseA
 
         switch (ph.p_type)
         {
+        case PT_NOTE:
+            m_noteStart = loadbias + ph.p_vaddr;
+            m_noteEnd = loadbias + ph.p_vaddr + ph.p_memsz;
+            break;
+
         case PT_DYNAMIC:
-            if (pdynamicAddr != nullptr) {
-                *pdynamicAddr = reinterpret_cast<Elf_Dyn*>(loadbias + ph.p_vaddr);
+            if (pdynamicAddr != nullptr)
+            {
+                if (m_isFileLayout)
+                {
+                    *pdynamicAddr = reinterpret_cast<Elf_Dyn*>(loadbias + ph.p_offset);
+                }
+                else
+                {
+                    *pdynamicAddr = reinterpret_cast<Elf_Dyn*>(loadbias + ph.p_vaddr);
+                }
             }
             break;
         }
index 67ddc0d8646530d138d73a4c8cc35db2ab374214..a30d880cc7ade8fc7a6ce42a25488cf1293d2874 100644 (file)
@@ -32,6 +32,7 @@ typedef struct {
 class ElfReader
 {
 private:
+    bool m_isFileLayout;
     void* m_gnuHashTableAddr;               // DT_GNU_HASH
     void* m_stringTableAddr;                // DT_STRTAB
     int m_stringTableSize;                  // DT_STRSIZ
@@ -41,8 +42,11 @@ private:
     int32_t* m_buckets;                     // gnu hash table buckets    
     void* m_chainsAddress;
 
+    uint64_t m_noteStart;
+    uint64_t m_noteEnd;
+
 public:
-    ElfReader();
+    ElfReader(bool isFileLayout);
     virtual ~ElfReader();
 #ifdef HOST_UNIX
     bool EnumerateElfInfo(ElfW(Phdr)* phdrAddr, int phnum);
@@ -50,6 +54,7 @@ public:
     bool PopulateForSymbolLookup(uint64_t baseAddress);
     bool TryLookupSymbol(std::string symbolName, uint64_t* symbolOffset);
     bool EnumerateProgramHeaders(uint64_t baseAddress, uint64_t* ploadbias = nullptr, ElfW(Dyn)** pdynamicAddr = nullptr);
+    bool GetBuildId(BYTE* buffer, ULONG bufferSize, PULONG pBuildSize);
 
 private:
     bool GetSymbol(int32_t index, ElfW(Sym)* symbol);
diff --git a/src/shared/dbgutil/machoreader.cpp b/src/shared/dbgutil/machoreader.cpp
new file mode 100644 (file)
index 0000000..a5b4434
--- /dev/null
@@ -0,0 +1,614 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#include <windows.h>
+#include <clrdata.h>
+#include <cor.h>
+#include <cordebug.h>
+#define __STDC_FORMAT_MACROS
+#include <inttypes.h>
+#include <arrayholder.h>
+#include "machoreader.h"
+
+#if TARGET_64BIT
+#define PRIx PRIx64
+#define PRIu PRIu64
+#define PRId PRId64
+#define PRIA "016"
+#define PRIxA PRIA PRIx
+#else
+#define PRIx PRIx32
+#define PRIu PRIu32
+#define PRId PRId32
+#define PRIA "08"
+#define PRIxA PRIA PRIx
+#endif
+
+class MachOReaderFromFile : public MachOReader
+{
+private:
+    PAL_FILE* m_file;
+
+public:
+    MachOReaderFromFile() :
+        m_file(NULL)
+    {
+    }
+
+    virtual ~MachOReaderFromFile()
+    {
+    }
+
+    bool OpenFile(const WCHAR* modulePath)
+    {
+        _ASSERTE(m_file == NULL);
+        m_file = _wfopen(modulePath, W("rb"));
+        return m_file != NULL;
+    }
+
+    virtual bool ReadMemory(void* address, void* buffer, size_t size)
+    {
+        if (m_file == NULL)
+        {
+            return false;
+        }
+        if (PAL_fseek(m_file, (LONG)(intptr_t)address, SEEK_SET) != 0)
+        {
+            return false;
+        }
+        size_t read = PAL_fread(buffer, 1, size, m_file);
+        return read > 0;
+    }
+};
+
+//
+// Entry point to get an export symbol from a module file
+//
+extern "C" bool
+TryReadSymbolFromFile(const WCHAR* modulePath, const char* symbolName, BYTE* buffer, ULONG32 size)
+{
+    MachOReaderFromFile reader;
+    if (reader.OpenFile(modulePath))
+    {
+        MachOModule module(reader, true, 0);
+        if (module.ReadHeader())
+        {
+            uint64_t symbolOffset;
+            if (module.TryLookupSymbol(symbolName, &symbolOffset))
+            {
+                return reader.ReadMemory((void*)symbolOffset, buffer, size);
+            }
+        }
+    }
+    return false;
+}
+
+//
+// Entry point to get the MachO file's build id
+//
+extern "C" bool
+TryGetBuildIdFromFile(const WCHAR* modulePath, BYTE* buffer, ULONG bufferSize, PULONG pBuildSize)
+{
+    MachOReaderFromFile reader;
+    if (reader.OpenFile(modulePath))
+    {
+        MachOModule module(reader, true, 0);
+        return module.GetBuildId(buffer, bufferSize, pBuildSize);
+    }
+    return false;
+}
+
+typedef bool (*ReadMemoryCallback)(void* address, void* buffer, size_t size);
+
+class MachOReaderWithCallback : public MachOReader
+{
+private:
+    ReadMemoryCallback m_readMemory;
+
+public:
+    MachOReaderWithCallback(ReadMemoryCallback readMemory) :
+        m_readMemory(readMemory)
+    {
+    }
+
+    virtual ~MachOReaderWithCallback()
+    {
+    }
+
+private:
+    virtual bool ReadMemory(void* address, void* buffer, size_t size)
+    {
+        return m_readMemory(address, buffer, size);
+    }
+};
+
+//
+// Entry point to get an export symbol
+//
+extern "C" bool
+TryGetSymbolWithCallback(ReadMemoryCallback readMemory, uint64_t baseAddress, const char* symbolName, uint64_t* symbolAddress)
+{
+    MachOReaderWithCallback reader(readMemory);
+    MachOModule module(reader, false, baseAddress);
+    if (module.ReadHeader())
+    {
+        uint64_t symbolOffset;
+        if (module.TryLookupSymbol(symbolName, &symbolOffset))
+        {
+            *symbolAddress = symbolOffset;
+            return true;
+        }
+    }
+    *symbolAddress = 0;
+    return false;
+}
+
+class MachOReaderExport : public MachOReader
+{
+private:
+    ICorDebugDataTarget* m_dataTarget;
+
+public:
+    MachOReaderExport(ICorDebugDataTarget* dataTarget) :
+        m_dataTarget(dataTarget)
+    {
+        dataTarget->AddRef();
+    }
+
+    virtual ~MachOReaderExport()
+    {
+        m_dataTarget->Release();
+    }
+
+private:
+    virtual bool ReadMemory(void* address, void* buffer, size_t size)
+    {
+        uint32_t read = 0;
+        return SUCCEEDED(m_dataTarget->ReadVirtual(reinterpret_cast<CLRDATA_ADDRESS>(address), reinterpret_cast<PBYTE>(buffer), (uint32_t)size, &read));
+    }
+};
+
+//
+// Main entry point to get an export symbol
+//
+extern "C" bool
+TryGetSymbol(ICorDebugDataTarget* dataTarget, uint64_t baseAddress, const char* symbolName, uint64_t* symbolAddress)
+{
+    MachOReaderExport reader(dataTarget);
+    MachOModule module(reader, false, baseAddress);
+    if (module.ReadHeader())
+    {
+        uint64_t symbolOffset;
+        if (module.TryLookupSymbol(symbolName, &symbolOffset))
+        {
+            *symbolAddress = symbolOffset;
+            return true;
+        }
+    }
+    *symbolAddress = 0;
+    return false;
+}
+
+//
+// Get the build id of the module from a data target
+//
+extern "C" bool
+TryGetBuildId(ICorDebugDataTarget* dataTarget, uint64_t baseAddress, BYTE* buffer, ULONG bufferSize, PULONG pBuildSize)
+{
+    MachOReaderExport reader(dataTarget);
+    MachOModule module(reader, false, baseAddress);
+    return module.GetBuildId(buffer, bufferSize, pBuildSize);
+}
+
+//--------------------------------------------------------------------
+// MachO module 
+//--------------------------------------------------------------------
+
+MachOModule::MachOModule(MachOReader& reader, bool isFileLayout, mach_vm_address_t baseAddress, mach_header_64* header, std::string* name) :
+    m_reader(reader),
+    m_isFileLayout(isFileLayout),
+    m_baseAddress(baseAddress),
+    m_loadBias(0),
+    m_commands(nullptr),
+    m_symtabCommand(nullptr),
+    m_nlists(nullptr),
+    m_strtabAddress(0)
+{
+    if (header != nullptr) {
+        m_header = *header;
+    }
+    if (name != nullptr) {
+        m_name = *name;
+    }
+}
+
+MachOModule::~MachOModule()
+{
+    if (m_commands != nullptr) {
+        free(m_commands);
+        m_commands = nullptr;
+    }
+    if (m_nlists != nullptr) {
+        free(m_nlists);
+        m_nlists = nullptr;
+    }
+}
+
+bool
+MachOModule::ReadHeader()
+{
+    _ASSERTE(sizeof(m_header) == sizeof(mach_header_64));
+    if (!m_reader.ReadMemory((void*)m_baseAddress, &m_header, sizeof(mach_header_64)))
+    {
+        m_reader.Trace("ERROR: failed to read header at %p\n", (void*)m_baseAddress);
+        return false;
+    }
+    m_reader.Trace("ReadHeader: magic %08x cputype %08x ncmds %d sizeofcmds %d\n", m_header.magic, m_header.cputype, m_header.ncmds, m_header.sizeofcmds);
+    return m_header.magic == 0xfeedfacf;
+}
+
+bool
+MachOModule::TryLookupSymbol(const char* symbolName, uint64_t* symbolValue)
+{
+    _ASSERTE(symbolValue != nullptr);
+
+    if (ReadSymbolTable())
+    {
+        _ASSERTE(m_nlists != nullptr);
+        _ASSERTE(m_strtabAddress != 0);
+
+        // First, search just the "external" export symbols 
+        if (TryLookupSymbol(m_dysymtabCommand->iextdefsym, m_dysymtabCommand->nextdefsym, symbolName, symbolValue))
+        {
+            m_reader.Trace("SYM: Found '%s' in external symbols\n", symbolName);
+            return true;
+        }
+        m_reader.Trace("SYM: Missed '%s' in external symbols\n", symbolName);
+
+        // If not found in external symbols, search all of them
+        if (TryLookupSymbol(0, m_symtabCommand->nsyms, symbolName, symbolValue))
+        {
+            m_reader.Trace("SYM: Found '%s' in all symbols\n", symbolName);
+            return true;
+        }
+        m_reader.Trace("SYM: Missed '%s' in all symbols\n", symbolName);
+    }
+    *symbolValue = 0;
+    return false;
+}
+
+bool
+MachOModule::TryLookupSymbol(int start, int nsyms, const char* symbolName, uint64_t* symbolValue)
+{
+    for (int i = 0; i < nsyms; i++)
+    {
+        std::string name = GetSymbolName(start + i);
+
+        // Skip the leading underscores to match Linux externs
+        const char* currentName = name.length() > 0 && name[0] == '_' ? name.c_str() + 1 : name.c_str();
+
+        // Does this symbol match?
+        if (strcmp(currentName, symbolName) == 0)
+        {
+            *symbolValue = m_loadBias + m_nlists[start + i].n_value;
+            return true;
+        }
+    }
+    *symbolValue = 0;
+    return false;
+}
+
+bool 
+MachOModule::GetBuildId(BYTE* buffer, ULONG bufferSize, PULONG pBuildSize)
+{
+    _ASSERTE(pBuildSize != nullptr);
+    if (ReadHeader())
+    {
+        if (ReadLoadCommands())
+        { 
+            if (m_uuidCommand != nullptr)
+            {
+                *pBuildSize = sizeof(m_uuidCommand->uuid);
+                memcpy_s(buffer, bufferSize, m_uuidCommand->uuid, *pBuildSize);
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+bool
+MachOModule::EnumerateSegments()
+{
+    if (!ReadLoadCommands())
+    {
+        return false;
+    }
+    _ASSERTE(!m_segments.empty());
+
+    for (const segment_command_64* segment : m_segments)
+    {
+        m_reader.VisitSegment(*this, *segment);
+
+        const section_64* section = (section_64*)((uint64_t)segment + sizeof(segment_command_64));
+
+        for (int s = 0; s < segment->nsects; s++, section++)
+        {
+            m_reader.VisitSection(*this, *section);
+        }
+    }
+    return true;
+}
+
+bool
+MachOModule::ReadLoadCommands()
+{
+    if (m_commands == nullptr)
+    {
+        // Read load commands
+        void* commandsAddress = (void*)(m_baseAddress + sizeof(mach_header_64));
+        m_commands = (load_command*)malloc(m_header.sizeofcmds);
+        if (m_commands == nullptr)
+        {
+            m_reader.Trace("ERROR: Failed to allocate %d byte load commands\n", m_header.sizeofcmds);
+            return false;
+        }
+        if (!m_reader.ReadMemory(commandsAddress, m_commands, m_header.sizeofcmds))
+        {
+            m_reader.Trace("ERROR: Failed to read load commands at %p of %d\n", commandsAddress, m_header.sizeofcmds);
+            return false;
+        }
+        load_command* command = m_commands;
+
+        for (int i = 0; i < m_header.ncmds; i++)
+        {
+            m_reader.TraceVerbose("CMD: load command cmd %02x (%d) size %d\n", command->cmd, command->cmd, command->cmdsize);
+
+            switch (command->cmd)
+            {
+            case LC_UUID:
+                m_uuidCommand = (uuid_command*)command;
+                break;
+
+            case LC_SYMTAB:
+                m_symtabCommand = (symtab_command*)command;
+                break;
+
+            case LC_DYSYMTAB:
+                m_dysymtabCommand = (dysymtab_command*)command;
+                break;
+
+            case LC_SEGMENT_64:
+                segment_command_64* segment = (segment_command_64*)command;
+                m_segments.push_back(segment);
+
+                // Calculate the load bias for the module. This is the value to add to the vmaddr of a
+                // segment to get the actual address.
+                if (!m_isFileLayout)
+                {
+                    if (strcmp(segment->segname, SEG_TEXT) == 0)
+                    {
+                        m_loadBias = m_baseAddress - segment->vmaddr;
+                    }
+                }
+
+                m_reader.TraceVerbose("CMD: vmaddr %016llx vmsize %016llx fileoff %016llx filesize %016llx nsects %d max %c%c%c init %c%c%c %02x %s\n",
+                    segment->vmaddr,
+                    segment->vmsize,
+                    segment->fileoff,
+                    segment->filesize,
+                    segment->nsects,
+                    (segment->maxprot & VM_PROT_READ) ? 'r' : '-',
+                    (segment->maxprot & VM_PROT_WRITE) ? 'w' : '-',
+                    (segment->maxprot & VM_PROT_EXECUTE) ? 'x' : '-',
+                    (segment->initprot & VM_PROT_READ) ? 'r' : '-',
+                    (segment->initprot & VM_PROT_WRITE) ? 'w' : '-',
+                    (segment->initprot & VM_PROT_EXECUTE) ? 'x' : '-',
+                    segment->flags,
+                    segment->segname);
+
+                section_64* section = (section_64*)((uint64_t)segment + sizeof(segment_command_64));
+                for (int s = 0; s < segment->nsects; s++, section++)
+                {
+                    m_reader.TraceVerbose("     addr %016llx size %016llx off %08x align %02x flags %02x %s\n",
+                        section->addr,
+                        section->size,
+                        section->offset,
+                        section->align,
+                        section->flags,
+                        section->sectname);
+                }
+                break;
+            }
+            // Get next load command
+            command = (load_command*)((char*)command + command->cmdsize);
+        }
+        m_reader.TraceVerbose("CMD: load bias %016llx\n", m_loadBias);
+    }
+
+    return true;
+}
+
+bool
+MachOModule::ReadSymbolTable()
+{
+    if (m_nlists == nullptr)
+    {
+        if (!ReadLoadCommands())
+        {
+            return false;
+        }
+        _ASSERTE(m_symtabCommand != nullptr);
+        _ASSERTE(m_strtabAddress == 0);
+
+        m_reader.TraceVerbose("SYM: symoff %08x nsyms %d stroff %08x strsize %d iext %d next %d iundef %d nundef %d extref %d nextref %d\n",
+            m_symtabCommand->symoff,
+            m_symtabCommand->nsyms,
+            m_symtabCommand->stroff,
+            m_symtabCommand->strsize,
+            m_dysymtabCommand->iextdefsym,
+            m_dysymtabCommand->nextdefsym,
+            m_dysymtabCommand->iundefsym,
+            m_dysymtabCommand->nundefsym,
+            m_dysymtabCommand->extrefsymoff,
+            m_dysymtabCommand->nextrefsyms);
+
+        // Read the entire symbol part of symbol table. An array of "nlist" structs.
+        void* symbolTableAddress = (void*)GetAddressFromFileOffset(m_symtabCommand->symoff);
+        size_t symtabSize = sizeof(nlist_64) * m_symtabCommand->nsyms;
+        m_nlists = (nlist_64*)malloc(symtabSize);
+        if (m_nlists == nullptr)
+        {
+            m_reader.Trace("ERROR: Failed to allocate %zu byte symtab\n", symtabSize);
+            return false;
+        }
+        if (!m_reader.ReadMemory(symbolTableAddress, m_nlists, symtabSize))
+        {
+            m_reader.Trace("ERROR: Failed to read symtab at %p of %zu\n", symbolTableAddress, symtabSize);
+            return false;
+        }
+
+        // Save the symbol string table address.
+        m_strtabAddress = GetAddressFromFileOffset(m_symtabCommand->stroff);
+    }
+    return true;
+}
+
+uint64_t
+MachOModule::GetAddressFromFileOffset(uint32_t offset)
+{
+    _ASSERTE(!m_segments.empty());
+    if (!m_isFileLayout)
+    {
+        for (const segment_command_64* segment : m_segments)
+        {
+            if (offset >= segment->fileoff && offset < (segment->fileoff + segment->filesize))
+            {
+                return m_loadBias + offset + segment->vmaddr - segment->fileoff;
+            }
+        }
+    }
+    return m_loadBias + offset;
+}
+
+std::string
+MachOModule::GetSymbolName(int index)
+{
+    uint64_t symbolNameAddress = m_strtabAddress + m_nlists[index].n_un.n_strx;
+    std::string result;
+    while (true)
+    {
+        char c = 0;
+        if (!m_reader.ReadMemory((void*)symbolNameAddress, &c, sizeof(char)))
+        {
+            m_reader.Trace("ERROR: Failed to read string table at %p\n", (void*)symbolNameAddress);
+            break;
+        }
+        if (c == '\0')
+        {
+            break;
+        }
+        result.append(1, c);
+        symbolNameAddress++;
+    }
+    return result;
+}
+
+//--------------------------------------------------------------------
+// MachO reader
+//--------------------------------------------------------------------
+
+MachOReader::MachOReader()
+{
+}
+
+bool
+MachOReader::EnumerateModules(mach_vm_address_t address, mach_header_64* header)
+{
+    _ASSERTE(header->magic == MH_MAGIC_64);
+    _ASSERTE(header->filetype == MH_DYLINKER);
+
+    MachOModule dylinker(*this, false, address, header);
+
+    // Search for symbol for the dyld image info cache
+    uint64_t dyldInfoAddress = 0;
+    if (!dylinker.TryLookupSymbol("dyld_all_image_infos", &dyldInfoAddress))
+    {
+        Trace("ERROR: Can not find the _dyld_all_image_infos symbol\n");
+        return false;
+    }
+
+    // Read the all image info from the dylinker image
+    dyld_all_image_infos dyldInfo;
+
+    if (!ReadMemory((void*)dyldInfoAddress, &dyldInfo, sizeof(dyld_all_image_infos)))
+    {
+        Trace("ERROR: Failed to read dyld_all_image_infos at %p\n", (void*)dyldInfoAddress);
+        return false;
+    }
+    std::string dylinkerPath;
+    if (!ReadString(dyldInfo.dyldPath, dylinkerPath))
+    {
+        Trace("ERROR: Failed to read name at %p\n", dyldInfo.dyldPath);
+        return false;
+    }
+    dylinker.SetName(dylinkerPath);
+    Trace("MOD: %016llx %08x %s\n", dylinker.BaseAddress(), dylinker.Header().flags, dylinker.Name().c_str());
+    VisitModule(dylinker);
+
+    void* imageInfosAddress = (void*)dyldInfo.infoArray;
+    size_t imageInfosSize = dyldInfo.infoArrayCount * sizeof(dyld_image_info);
+    Trace("MOD: infoArray %p infoArrayCount %d\n", dyldInfo.infoArray, dyldInfo.infoArrayCount);
+
+    ArrayHolder<dyld_image_info> imageInfos = new (std::nothrow) dyld_image_info[dyldInfo.infoArrayCount];
+    if (imageInfos == nullptr)
+    {
+        Trace("ERROR: Failed to allocate %zu byte image infos\n", imageInfosSize);
+        return false;
+    }
+    if (!ReadMemory(imageInfosAddress, imageInfos, imageInfosSize))
+    {
+        Trace("ERROR: Failed to read dyld_all_image_infos at %p\n", imageInfosAddress);
+        return false;
+    }
+    for (int i = 0; i < dyldInfo.infoArrayCount; i++)
+    {
+        mach_vm_address_t imageAddress = (mach_vm_address_t)imageInfos[i].imageLoadAddress;
+        const char* imageFilePathAddress = imageInfos[i].imageFilePath;
+
+        std::string imagePath;
+        if (!ReadString(imageFilePathAddress, imagePath))
+        {
+            Trace("ERROR: Failed to read image name at %p\n", imageFilePathAddress);
+            continue;
+        }
+        MachOModule module(*this, false, imageAddress, nullptr, &imagePath);
+        if (!module.ReadHeader())
+        {
+            continue;
+        }
+        Trace("MOD: %016llx %08x %s\n", imageAddress, module.Header().flags, imagePath.c_str());
+        VisitModule(module);
+    }
+    return true;
+}
+
+bool
+MachOReader::ReadString(const char* address, std::string& str)
+{
+    for (int i = 0; i < MAX_LONGPATH; i++)
+    {
+        char c = 0;
+        if (!ReadMemory((void*)(address + i), &c, sizeof(char)))
+        {
+            Trace("ERROR: Failed to read string at %p\n", (void*)(address + i));
+            return false;
+        }
+        if (c == '\0')
+        {
+            break;
+        }
+        str.append(1, c);
+    }
+    return true;
+}
\ No newline at end of file
diff --git a/src/shared/dbgutil/machoreader.h b/src/shared/dbgutil/machoreader.h
new file mode 100644 (file)
index 0000000..2492705
--- /dev/null
@@ -0,0 +1,70 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#include <mach/mach.h>
+#include <mach-o/loader.h>
+#include <mach-o/nlist.h>
+#include <mach-o/dyld_images.h>
+#include <string>
+#include <vector>
+
+class MachOReader;
+
+class MachOModule
+{
+    friend MachOReader;
+private:
+    MachOReader& m_reader;
+    bool m_isFileLayout;
+    mach_vm_address_t m_baseAddress;
+    mach_vm_address_t m_loadBias;
+    mach_header_64 m_header;
+    std::string m_name;
+    load_command* m_commands;
+    std::vector<segment_command_64*> m_segments;
+    uuid_command* m_uuidCommand;
+    symtab_command* m_symtabCommand;
+    dysymtab_command* m_dysymtabCommand;
+    nlist_64* m_nlists;
+    uint64_t m_strtabAddress;
+
+public:
+    MachOModule(MachOReader& reader, bool isFileLayout, mach_vm_address_t baseAddress, mach_header_64* header = nullptr, std::string* name = nullptr);
+    ~MachOModule();
+
+    inline mach_vm_address_t BaseAddress() const { return m_baseAddress; }
+    inline mach_vm_address_t LoadBias() const { return m_loadBias; }
+    inline const mach_header_64& Header() const { return m_header; }
+    inline const std::string& Name() const { return m_name; }
+
+    bool ReadHeader();
+    bool TryLookupSymbol(const char* symbolName, uint64_t* symbolValue);
+    bool TryLookupSymbol(int start, int nsyms, const char* symbolName, uint64_t* symbolValue);
+    bool GetBuildId(BYTE* buffer, ULONG bufferSize, PULONG pBuildSize);
+    bool EnumerateSegments();
+
+private:
+    inline void SetName(std::string& name) { m_name = name; }
+
+    bool ReadSymbolTable();
+    bool ReadLoadCommands();
+    uint64_t GetAddressFromFileOffset(uint32_t offset);
+    std::string GetSymbolName(int index);
+};
+
+class MachOReader
+{
+    friend MachOModule;
+public:
+    MachOReader();
+    bool EnumerateModules(mach_vm_address_t address, mach_header_64* header);
+
+private:
+    bool ReadString(const char* address, std::string& str);
+    virtual void VisitModule(MachOModule& module) { };
+    virtual void VisitSegment(MachOModule& module, const segment_command_64& segment) { };
+    virtual void VisitSection(MachOModule& module, const section_64& section) { };
+    virtual bool ReadMemory(void* address, void* buffer, size_t size) = 0;
+    virtual void Trace(const char* format, ...) { };
+    virtual void TraceVerbose(const char* format, ...) { };
+};
index ef15240933e38d11129009028798f90995698c0f..23d2a3ab9979ecdf3b10bce1f90adb96d66c2785 100644 (file)
@@ -83,7 +83,7 @@
   </PropertyGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <ClCompile>
-      <AdditionalIncludeDirectories>..\..\SOS\dbgutil;..\..\pal\prebuilt\inc;..\..\inc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>$(RepoRoot)artifacts\obj;$(RepoRoot)src\shared;$(RepoRoot)src\shared\inc;$(RepoRoot)src\shared\pal\prebuilt\inc;$(RepoRoot)src\shared\pal\inc;$(RepoRoot)src\shared\pal\inc\rt;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <AdditionalOptions>%(AdditionalOptions) /d2Zi+ /Zm200 /ZH:SHA_256 /source-charset:utf-8 /homeparams</AdditionalOptions>
       <AssemblerListingLocation>Debug/</AssemblerListingLocation>
       <BufferSecurityCheck>true</BufferSecurityCheck>
       <WarningLevel>Level3</WarningLevel>
       <PreprocessorDefinitions>DEBUG;_DEBUG;_DBG;URTBLDENV_FRIENDLY=Checked;BUILDENV_CHECKED=1;_AMD64_;_WIN64;AMD64;BIT64=1;_TARGET_64BIT_=1;_TARGET_AMD64_=1;DBG_TARGET_64BIT=1;DBG_TARGET_AMD64=1;DBG_TARGET_WIN64=1;WIN32;_WIN32;WINVER=0x0602;_WIN32_WINNT=0x0602;WIN32_LEAN_AND_MEAN=1;_CRT_SECURE_NO_WARNINGS;FEATURE_COMINTEROP;FEATURE_HIJACK;_SECURE_SCL=0;CMAKE_INTDIR="Debug";%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <ObjectFileName>$(IntDir)</ObjectFileName>
+      <BrowseInformation>true</BrowseInformation>
     </ClCompile>
     <ResourceCompile>
       <PreprocessorDefinitions>WIN32;_DEBUG;DEBUG;_DBG;URTBLDENV_FRIENDLY=Checked;BUILDENV_CHECKED=1;_AMD64_;_WIN64;AMD64;BIT64=1;_TARGET_64BIT_=1;_TARGET_AMD64_=1;DBG_TARGET_64BIT=1;DBG_TARGET_AMD64=1;DBG_TARGET_WIN64=1;_WIN32;WINVER=0x0602;_WIN32_WINNT=0x0602;WIN32_LEAN_AND_MEAN=1;_CRT_SECURE_NO_WARNINGS;;FEATURE_COMINTEROP;FEATURE_HIJACK;_SECURE_SCL=0;CMAKE_INTDIR=\"Debug\";%(PreprocessorDefinitions)</PreprocessorDefinitions>
     <Lib>
       <AdditionalOptions>%(AdditionalOptions) /machine:x64 /IGNORE:4221</AdditionalOptions>
     </Lib>
+    <Bscmake>
+      <PreserveSbr>true</PreserveSbr>
+    </Bscmake>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Checked|x64'">
     <ClCompile>
   <ImportGroup Label="ExtensionTargets">
     <Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
   </ImportGroup>
-</Project>
+</Project>
\ No newline at end of file
index 677ffb3fe7a60c196043cc3189cef3d524389b72..057306a18ef16fafe18a37d4fc3c1b510411e0c9 100644 (file)
@@ -1,5 +1,6 @@
 
-set( CORGUIDS_IDL_SOURCES
+set(CORGUIDS_IDL_SOURCES
+  metahost.idl
   cordebug.idl
   xcordebug.idl
   clrdata.idl
index 5cb3f82da2141226e7732af3ba4520c2ce29e1e1..d820a28265f3112aeedc9e004000e7667807ee4d 100644 (file)
@@ -1268,5 +1268,30 @@ enum {
   VER_NEED_CURRENT = 1
 };
 
+/* Note section contents.  Each entry in the note section begins with
+   a header of a fixed form.  */
+
+typedef struct
+{
+  Elf32_Word n_namesz;                 /* Length of the note's name.  */
+  Elf32_Word n_descsz;                 /* Length of the note's descriptor.  */
+  Elf32_Word n_type;                   /* Type of the note.  */
+} Elf32_Nhdr;
+
+typedef struct
+{
+  Elf64_Word n_namesz;                 /* Length of the note's name.  */
+  Elf64_Word n_descsz;                 /* Length of the note's descriptor.  */
+  Elf64_Word n_type;                   /* Type of the note.  */
+} Elf64_Nhdr;
+
+/* Known names of notes.  */
+
+/* Note entries for GNU systems have this name.  */
+#define ELF_NOTE_GNU           "GNU"
+
+/* Build ID bits as generated by ld --build-id.
+   The descriptor consists of any nonzero number of bytes.  */
+#define NT_GNU_BUILD_ID        3
 
 #endif
index 5bc51f6d133e263e79a0f63cf4e93c5c8bc28414..3b455b1477b7e3555fb41b6c49a1716873256a16 100644 (file)
@@ -60,6 +60,9 @@ cpp_quote("EXTERN_GUID(IID_ICLRDebuggingLibraryProvider, 0x3151c08d, 0x4d09, 0x4
 // IID ICLRDebuggingLibraryProvider2 interface : uuid{E04E2FF1-DCFD-45D5-BCD1-16FFF2FAF7BA}
 cpp_quote("EXTERN_GUID(IID_ICLRDebuggingLibraryProvider2, 0xE04E2FF1, 0xDCFD, 0x45D5, 0xBC, 0xD1, 0x16, 0xFF, 0xF2, 0xFA, 0xF7, 0xBA);")
 
+// IID ICLRDebuggingLibraryProvider3 interface : uuid{DE3AAB18-46A0-48B4-BF0D-2C336E69EA1B}
+cpp_quote("EXTERN_GUID(IID_ICLRDebuggingLibraryProvider3, 0xde3aab18, 0x46a0, 0x48b4, 0xbf, 0xd, 0x2c, 0x33, 0x6e, 0x69, 0xea, 0x1b);")
+
 // For use in ICLRMetaHost::RequestRuntimeLoadedNotification
 interface ICLRRuntimeInfo;
 
@@ -71,6 +74,13 @@ typedef void (__stdcall *RuntimeLoadedCallbackFnPtr)(
     CallbackThreadSetFnPtr    pfnCallbackThreadSet,
     CallbackThreadUnsetFnPtr  pfnCallbackThreadUnset);
 
+typedef enum 
+{
+    Unknown = 0,
+    Identity = 1,
+    Runtime = 2,
+} LIBRARY_PROVIDER_INDEX_TYPE;
+
 /**************************************************************************************
  ** ICLRMetaHost                                                                     **
  ** Activated using mscoree!CLRCreateInstance. Does not do any policy decisions, get **
@@ -290,6 +300,104 @@ interface ICLRDebuggingLibraryProvider2 : IUnknown
         [out] LPWSTR* ppResolvedModulePath);
 }
 
+ /**************************************************************************************
+ ** ICLRDebuggingLibraryProvider3                                                     **
+ ** Implemented by API user                                                           **
+ ** This interface allows the debugger to provide module paths which are needed for   **
+ ** debugging a particular CLR such as mscordbi and mscordacwks.                      **
+ **************************************************************************************/
+[
+    uuid(DE3AAB18-46A0-48B4-BF0D-2C336E69EA1B),
+    version(1.0),
+    helpstring("CLR debugging LibraryProvider callback interface"),
+    local
+]
+interface ICLRDebuggingLibraryProvider3 : IUnknown
+{
+    /**********************************************************************************
+    ** The goal of this method is to allow the debugger to provide a module path     **
+    ** which is needed for debugging. The debugger may use any available means to    **
+    ** locate and/or procure the module. See the security note below for important   **
+    ** information about implementing this method securely.                          **
+    ** Arguments:                                                                    **
+    ** pwzFileName - The name of the module being requested                          **
+    **                                                                               **
+    ** pwszRuntimeModule - The runtime module path or the module path of the         **
+    **                     single-file app. NULL when called from the                **
+    **                     OpenVirtualProcess API.                                   **
+    **                                                                               **
+    ** indexType - The type of time stamp/file size is either the identity of the    **
+    **             requested module or the runtime (coreclr) module's.               **
+    **                                                                               **
+    ** dwTimeStamp - The date time stamp stored in the COFF file header of PE files  **
+    **                                                                               **
+    ** dwSizeOfImage - The SizeOfImage field stored in the COFF optional file header **
+    **                 of PE files                                                   **
+    **                                                                               **
+    ** ppResolvedModulePath - Where *ppResolvedModulePath is a null terminated       **
+    **                        path to the module dll. On Windows it should be        **
+    **                        allocated with CoTaskMemAlloc. On Unix it should be    **
+    **                        allocated with malloc. Failure leave it untouched. See **
+    **                        security note below!                                   **
+    **                                                                               **
+    ** Return value - S_OK if the module was provided, or any other convenient       **
+    **                error HRESULT if the module could not be provided              **
+    **                                                                               **
+    **                               !!!!!!!!!!!!!!                                  **
+    ** SECURITY NOTE: Anything the caller would not be willing to execute itself, it **
+    ** should not provide to the this API call                                       **
+    ***********************************************************************************/
+    HRESULT ProvideWindowsLibrary(
+        [in] const WCHAR* pwszFileName,
+        [in] const WCHAR* pwszRuntimeModule,
+        [in] LIBRARY_PROVIDER_INDEX_TYPE indexType,
+        [in] DWORD dwTimestamp,
+        [in] DWORD dwSizeOfImage,
+        [out] LPWSTR* ppResolvedModulePath);
+
+    /**********************************************************************************
+    ** The goal of this method is to allow the debugger to provide a module path     **
+    ** which is needed for debugging. The debugger may use any available means to    **
+    ** locate and/or procure the module. See the security note below for important   **
+    ** information about implementing this method securely.                          **
+    ** Arguments:                                                                    **
+    ** pwszFileName - The name of the module being requested                         **
+    **                                                                               **
+    ** pwszRuntimeModule - The runtime module path or the module path of the         **
+    **                     single-file app. NULL when called from the                **
+    **                     OpenVirtualProcess API.                                   **
+    **                                                                               **
+    ** indexType - The type of the build id either the identity of the requested     **
+    **             module or the runtime (coreclr) module's. Normally it will        **
+    **             be the identity index type and the runtime index for Linux        **
+    **             cross-DAC on Windows or for non-singlefile apps on Linux/MacOS.   **
+    **                                                                               **
+    ** pbBuildId - The Linux or MacOS module build id                                **
+    **                                                                               **
+    ** iBuildIdSize - The number of bytes in the build id                            **
+    **                                                                               **
+    ** ppResolvedModulePath - Where *ppResolvedModulePath is a null terminated       **
+    **                        path to the module dll. On Windows it should be        **
+    **                        allocated with CoTaskMemAlloc. On Unix it should be    **
+    **                        allocated with malloc. Failure leave it untouched. See **
+    **                        security note below!                                   **
+    **                                                                               **
+    ** Return value - S_OK if the module was provided, or any other convenient       **
+    **                error HRESULT if the module could not be provided              **
+    **                                                                               **
+    **                               !!!!!!!!!!!!!!                                  **
+    ** SECURITY NOTE: Anything the caller would not be willing to execute itself, it **
+    ** should not provide to the this API call                                       **
+    ***********************************************************************************/
+    HRESULT ProvideUnixLibrary(
+        [in] const WCHAR* pwszFileName,
+        [in] const WCHAR* pwszRuntimeModule,
+        [in] LIBRARY_PROVIDER_INDEX_TYPE indexType,
+        [in] BYTE* pbBuildId,
+        [in] int iBuildIdSize,
+        [out] LPWSTR* ppResolvedModulePath);
+}
+
 /**************************************************************************************
  ** ICLRDebugging                                                                    **
  ** Activated using mscoree!CLRCreateInstance.                                       **
index a5484c6b81618baf5abc5dd27172595c4ea8ec96..ef493ff3861547ae89a30f9d340e56318b2c9927 100644 (file)
@@ -13,11 +13,11 @@ typedef unsigned char SYMBOL_INDEX;
 //    - Update the logic in ClrDataAccess::EnumMemCLRMainModuleInfo to ensure all needed state is in the dump.
 typedef struct _RuntimeInfo
 {
-    const char Signature[18];
+    char Signature[18];
     int Version;
-    const SYMBOL_INDEX RuntimeModuleIndex[24];
-    const SYMBOL_INDEX DacModuleIndex[24];
-    const SYMBOL_INDEX DbiModuleIndex[24];
+    SYMBOL_INDEX RuntimeModuleIndex[24];
+    SYMBOL_INDEX DacModuleIndex[24];
+    SYMBOL_INDEX DbiModuleIndex[24];
 } RuntimeInfo;
 
 extern RuntimeInfo DotNetRuntimeInfo;
index 979c8cdcc3b050e651caf32a9356874f6561ee66..01e0851cc5ae542780a0365d8c89735f16da7e07 100644 (file)
@@ -366,8 +366,9 @@ PALIMPORT
 int
 PALAPI
 PAL_InitializeDLL();
-typedef VOID (*PPAL_STARTUP_CALLBACK)(
-    char *modulePath,
+
+typedef bool (*PPAL_STARTUP_CALLBACK)(
+    const char *modulePath,
     HMODULE hModule,
     PVOID parameter);
 
diff --git a/src/shared/pal/prebuilt/idl/metahost_i.cpp b/src/shared/pal/prebuilt/idl/metahost_i.cpp
new file mode 100644 (file)
index 0000000..285aa87
--- /dev/null
@@ -0,0 +1,92 @@
+
+
+/* this ALWAYS GENERATED file contains the IIDs and CLSIDs */
+
+/* link this file in with the server and any clients */
+
+
+ /* File created by MIDL compiler version 8.01.0622 */
+/* Compiler settings for metahost.idl:
+    Oicf, W1, Zp8, env=Win32 (32b run), target_arch=X86 8.01.0622 
+    protocol : dce , ms_ext, c_ext, robust
+    error checks: allocation ref bounds_check enum stub_data 
+    VC __declspec() decoration level: 
+         __declspec(uuid()), __declspec(selectany), __declspec(novtable)
+         DECLSPEC_UUID(), MIDL_INTERFACE()
+*/
+/* @@MIDL_FILE_HEADING(  ) */
+
+#pragma warning( disable: 4049 )  /* more than 64k source lines */
+
+
+#ifdef __cplusplus
+extern "C"{
+#endif 
+
+
+#include <rpc.h>
+#include <rpcndr.h>
+
+#ifdef _MIDL_USE_GUIDDEF_
+
+#ifndef INITGUID
+#define INITGUID
+#include <guiddef.h>
+#undef INITGUID
+#else
+#include <guiddef.h>
+#endif
+
+#define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \
+        DEFINE_GUID(name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8)
+
+#else // !_MIDL_USE_GUIDDEF_
+
+#ifndef __IID_DEFINED__
+#define __IID_DEFINED__
+
+typedef struct _IID
+{
+    unsigned long x;
+    unsigned short s1;
+    unsigned short s2;
+    unsigned char  c[8];
+} IID;
+
+#endif // __IID_DEFINED__
+
+#ifndef CLSID_DEFINED
+#define CLSID_DEFINED
+typedef IID CLSID;
+#endif // CLSID_DEFINED
+
+#define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \
+        EXTERN_C __declspec(selectany) const type name = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}
+
+#endif // !_MIDL_USE_GUIDDEF_
+
+MIDL_DEFINE_GUID(IID, IID_ICLRMetaHost,0xD332DB9E,0xB9B3,0x4125,0x82,0x07,0xA1,0x48,0x84,0xF5,0x32,0x16);
+
+
+MIDL_DEFINE_GUID(IID, IID_ICLRDebuggingLibraryProvider,0x3151C08D,0x4D09,0x4f9b,0x88,0x38,0x28,0x80,0xBF,0x18,0xFE,0x51);
+
+
+MIDL_DEFINE_GUID(IID, IID_ICLRDebuggingLibraryProvider2,0xE04E2FF1,0xDCFD,0x45D5,0xBC,0xD1,0x16,0xFF,0xF2,0xFA,0xF7,0xBA);
+
+
+MIDL_DEFINE_GUID(IID, IID_ICLRDebuggingLibraryProvider3,0xDE3AAB18,0x46A0,0x48B4,0xBF,0x0D,0x2C,0x33,0x6E,0x69,0xEA,0x1B);
+
+
+MIDL_DEFINE_GUID(IID, IID_ICLRDebugging,0xD28F3C5A,0x9634,0x4206,0xA5,0x09,0x47,0x75,0x52,0xEE,0xFB,0x10);
+
+
+MIDL_DEFINE_GUID(IID, IID_ICLRRuntimeInfo,0xBD39D1D2,0xBA2F,0x486a,0x89,0xB0,0xB4,0xB0,0xCB,0x46,0x68,0x91);
+
+#undef MIDL_DEFINE_GUID
+
+#ifdef __cplusplus
+}
+#endif
+
+
+
index aae25d70db1b2bb8b155e4ded09e3897451ddd73..069d71da52f59be3ca73ee00b84309489f5fde58 100644 (file)
@@ -4,8 +4,6 @@
 
 
  /* File created by MIDL compiler version 8.01.0622 */
-/* at Mon Jan 18 19:14:07 2038
- */
 /* Compiler settings for metahost.idl:
     Oicf, W1, Zp8, env=Win32 (32b run), target_arch=X86 8.01.0622 
     protocol : dce , ms_ext, c_ext, robust
@@ -66,6 +64,13 @@ typedef interface ICLRDebuggingLibraryProvider2 ICLRDebuggingLibraryProvider2;
 #endif         /* __ICLRDebuggingLibraryProvider2_FWD_DEFINED__ */
 
 
+#ifndef __ICLRDebuggingLibraryProvider3_FWD_DEFINED__
+#define __ICLRDebuggingLibraryProvider3_FWD_DEFINED__
+typedef interface ICLRDebuggingLibraryProvider3 ICLRDebuggingLibraryProvider3;
+
+#endif         /* __ICLRDebuggingLibraryProvider3_FWD_DEFINED__ */
+
+
 #ifndef __ICLRDebugging_FWD_DEFINED__
 #define __ICLRDebugging_FWD_DEFINED__
 typedef interface ICLRDebugging ICLRDebugging;
@@ -103,6 +108,7 @@ EXTERN_GUID(CLSID_CLRDebugging, 0xbacc578d, 0xfbdd, 0x48a4, 0x96, 0x9f, 0x2, 0xd
 EXTERN_GUID(IID_ICLRRuntimeInfo, 0xBD39D1D2, 0xBA2F, 0x486a, 0x89, 0xB0, 0xB4, 0xB0, 0xCB, 0x46, 0x68, 0x91);
 EXTERN_GUID(IID_ICLRDebuggingLibraryProvider, 0x3151c08d, 0x4d09, 0x4f9b, 0x88, 0x38, 0x28, 0x80, 0xbf, 0x18, 0xfe, 0x51);
 EXTERN_GUID(IID_ICLRDebuggingLibraryProvider2, 0xE04E2FF1, 0xDCFD, 0x45D5, 0xBC, 0xD1, 0x16, 0xFF, 0xF2, 0xFA, 0xF7, 0xBA);
+EXTERN_GUID(IID_ICLRDebuggingLibraryProvider3, 0xde3aab18, 0x46a0, 0x48b4, 0xbf, 0xd, 0x2c, 0x33, 0x6e, 0x69, 0xea, 0x1b);
 
 typedef HRESULT ( __stdcall *CallbackThreadSetFnPtr )( void);
 
@@ -113,6 +119,14 @@ typedef void ( __stdcall *RuntimeLoadedCallbackFnPtr )(
     CallbackThreadSetFnPtr pfnCallbackThreadSet,
     CallbackThreadUnsetFnPtr pfnCallbackThreadUnset);
 
+typedef /* [public][public][public] */ 
+enum __MIDL___MIDL_itf_metahost_0000_0000_0001
+    {
+        Unknown        = 0,
+        Identity       = 1,
+        Runtime        = 2
+    }  LIBRARY_PROVIDER_INDEX_TYPE;
+
 
 
 extern RPC_IF_HANDLE __MIDL_itf_metahost_0000_0000_v0_0_c_ifspec;
@@ -468,6 +482,116 @@ EXTERN_C const IID IID_ICLRDebuggingLibraryProvider2;
 #endif         /* __ICLRDebuggingLibraryProvider2_INTERFACE_DEFINED__ */
 
 
+#ifndef __ICLRDebuggingLibraryProvider3_INTERFACE_DEFINED__
+#define __ICLRDebuggingLibraryProvider3_INTERFACE_DEFINED__
+
+/* interface ICLRDebuggingLibraryProvider3 */
+/* [object][local][helpstring][version][uuid] */ 
+
+
+EXTERN_C const IID IID_ICLRDebuggingLibraryProvider3;
+
+#if defined(__cplusplus) && !defined(CINTERFACE)
+    
+    MIDL_INTERFACE("DE3AAB18-46A0-48B4-BF0D-2C336E69EA1B")
+    ICLRDebuggingLibraryProvider3 : public IUnknown
+    {
+    public:
+        virtual HRESULT STDMETHODCALLTYPE ProvideWindowsLibrary( 
+            /* [in] */ const WCHAR *pwszFileName,
+            /* [in] */ const WCHAR *pwszRuntimeModule,
+            /* [in] */ LIBRARY_PROVIDER_INDEX_TYPE indexType,
+            /* [in] */ DWORD dwTimestamp,
+            /* [in] */ DWORD dwSizeOfImage,
+            /* [out] */ LPWSTR *ppResolvedModulePath) = 0;
+        
+        virtual HRESULT STDMETHODCALLTYPE ProvideUnixLibrary( 
+            /* [in] */ const WCHAR *pwszFileName,
+            /* [in] */ const WCHAR *pwszRuntimeModule,
+            /* [in] */ LIBRARY_PROVIDER_INDEX_TYPE indexType,
+            /* [in] */ BYTE *pbBuildId,
+            /* [in] */ int iBuildIdSize,
+            /* [out] */ LPWSTR *ppResolvedModulePath) = 0;
+        
+    };
+    
+    
+#else  /* C style interface */
+
+    typedef struct ICLRDebuggingLibraryProvider3Vtbl
+    {
+        BEGIN_INTERFACE
+        
+        HRESULT ( STDMETHODCALLTYPE *QueryInterface )( 
+            ICLRDebuggingLibraryProvider3 * This,
+            /* [in] */ REFIID riid,
+            /* [annotation][iid_is][out] */ 
+            _COM_Outptr_  void **ppvObject);
+        
+        ULONG ( STDMETHODCALLTYPE *AddRef )( 
+            ICLRDebuggingLibraryProvider3 * This);
+        
+        ULONG ( STDMETHODCALLTYPE *Release )( 
+            ICLRDebuggingLibraryProvider3 * This);
+        
+        HRESULT ( STDMETHODCALLTYPE *ProvideWindowsLibrary )( 
+            ICLRDebuggingLibraryProvider3 * This,
+            /* [in] */ const WCHAR *pwszFileName,
+            /* [in] */ const WCHAR *pwszRuntimeModule,
+            /* [in] */ LIBRARY_PROVIDER_INDEX_TYPE indexType,
+            /* [in] */ DWORD dwTimestamp,
+            /* [in] */ DWORD dwSizeOfImage,
+            /* [out] */ LPWSTR *ppResolvedModulePath);
+        
+        HRESULT ( STDMETHODCALLTYPE *ProvideUnixLibrary )( 
+            ICLRDebuggingLibraryProvider3 * This,
+            /* [in] */ const WCHAR *pwszFileName,
+            /* [in] */ const WCHAR *pwszRuntimeModule,
+            /* [in] */ LIBRARY_PROVIDER_INDEX_TYPE indexType,
+            /* [in] */ BYTE *pbBuildId,
+            /* [in] */ int iBuildIdSize,
+            /* [out] */ LPWSTR *ppResolvedModulePath);
+        
+        END_INTERFACE
+    } ICLRDebuggingLibraryProvider3Vtbl;
+
+    interface ICLRDebuggingLibraryProvider3
+    {
+        CONST_VTBL struct ICLRDebuggingLibraryProvider3Vtbl *lpVtbl;
+    };
+
+    
+
+#ifdef COBJMACROS
+
+
+#define ICLRDebuggingLibraryProvider3_QueryInterface(This,riid,ppvObject)      \
+    ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) 
+
+#define ICLRDebuggingLibraryProvider3_AddRef(This)     \
+    ( (This)->lpVtbl -> AddRef(This) ) 
+
+#define ICLRDebuggingLibraryProvider3_Release(This)    \
+    ( (This)->lpVtbl -> Release(This) ) 
+
+
+#define ICLRDebuggingLibraryProvider3_ProvideWindowsLibrary(This,pwszFileName,pwszRuntimeModule,indexType,dwTimestamp,dwSizeOfImage,ppResolvedModulePath)      \
+    ( (This)->lpVtbl -> ProvideWindowsLibrary(This,pwszFileName,pwszRuntimeModule,indexType,dwTimestamp,dwSizeOfImage,ppResolvedModulePath) ) 
+
+#define ICLRDebuggingLibraryProvider3_ProvideUnixLibrary(This,pwszFileName,pwszRuntimeModule,indexType,pbBuildId,iBuildIdSize,ppResolvedModulePath)    \
+    ( (This)->lpVtbl -> ProvideUnixLibrary(This,pwszFileName,pwszRuntimeModule,indexType,pbBuildId,iBuildIdSize,ppResolvedModulePath) ) 
+
+#endif /* COBJMACROS */
+
+
+#endif         /* C style interface */
+
+
+
+
+#endif         /* __ICLRDebuggingLibraryProvider3_INTERFACE_DEFINED__ */
+
+
 #ifndef __ICLRDebugging_INTERFACE_DEFINED__
 #define __ICLRDebugging_INTERFACE_DEFINED__
 
index 32357c40b7735f7473b71f0a9e5e38d94aa2e801..4cf647e3adfbbd19fb32003c873e5e02e37d8f4c 100644 (file)
@@ -38,9 +38,14 @@ namespace CorUnix
     //
     struct ProcessModules
     {
-        ProcessModules *Next;
-        PVOID BaseAddress;
-        CHAR Name[0];
+        ProcessModules* _next;
+        PVOID _baseAddress;
+        PVOID _minimumAddress;
+        CHAR _name[0];
+
+        ProcessModules* GetNext() const { return _next; }
+        PVOID GetBaseAddress() const { return _baseAddress == 0 ? _minimumAddress : _baseAddress; }
+        const CHAR* GetName() const { return _name; }
     };
 
     //
index 294c12fc6424f2fb03d1ed4bb055c6981009aa64..80de0941b6cdeb66c3f60e1e1a3953c0f8530b22 100644 (file)
@@ -1493,26 +1493,30 @@ public:
         listHead = CreateProcessModules(m_processId, &count);
         if (listHead == NULL)
         {
-            TRACE("CreateProcessModules failed for pid %d\n", m_processId);
+            ERROR("CreateProcessModules failed for pid %d\n", m_processId);
             pe = ERROR_INVALID_PARAMETER;
             goto exit;
         }
 
-        for (ProcessModules *entry = listHead; entry != NULL; entry = entry->Next)
+        for (ProcessModules *entry = listHead; entry != NULL; entry = entry->GetNext())
         {
-            if (IsCoreClrModule(entry->Name))
+            bool found = false;
+
+            PAL_CPP_TRY
             {
-                PAL_CPP_TRY
-                {
-                    TRACE("InvokeStartupCallback executing callback %p %s\n", entry->BaseAddress, entry->Name);
-                    m_callback(entry->Name, entry->BaseAddress, m_parameter);
-                }
-                PAL_CPP_CATCH_ALL
-                {
-                }
-                PAL_CPP_ENDTRY
+                found = m_callback(entry->GetName(), entry->GetBaseAddress(), m_parameter);
+            }
+            PAL_CPP_CATCH_ALL
+            {
+                ERROR("InvokeStartupCallback exception in callback %p %s\n", entry->GetBaseAddress(), entry->GetName());
+                pe = ERROR_ACCESS_DENIED;
+                goto exit;
+            }
+            PAL_CPP_ENDTRY
 
-                // Currently only the first coreclr module in a process is supported
+            if (found)
+            {
+                TRACE("InvokeStartupCallback found module %p %s\n", entry->GetBaseAddress(), entry->GetName());
                 break;
             }
         }
@@ -2118,14 +2122,14 @@ EnumProcessModules(
     ProcessModules *listHead = GetProcessModulesFromHandle(hProcess, &count);
     if (listHead != NULL)
     {
-        for (ProcessModules *entry = listHead; entry != NULL; entry = entry->Next)
+        for (ProcessModules *entry = listHead; entry != NULL; entry = entry->GetNext())
         {
             if (cb <= 0)
             {
                 break;
             }
             cb -= sizeof(HMODULE);
-            *lphModule = (HMODULE)entry->BaseAddress;
+            *lphModule = (HMODULE)entry->GetBaseAddress();
             lphModule++;
         }
     }
@@ -2169,12 +2173,12 @@ GetModuleFileNameExW(
     ProcessModules *listHead = GetProcessModulesFromHandle(hProcess, &count);
     if (listHead != NULL)
     {
-        for (ProcessModules *entry = listHead; entry != NULL; entry = entry->Next)
+        for (ProcessModules *entry = listHead; entry != NULL; entry = entry->GetNext())
         {
-            if ((HMODULE)entry->BaseAddress == hModule)
+            if ((HMODULE)entry->GetBaseAddress() == hModule)
             {
                 // Convert CHAR string into WCHAR string
-                result = MultiByteToWideChar(CP_ACP, 0, entry->Name, -1, lpFilename, nSize);
+                result = MultiByteToWideChar(CP_ACP, 0, entry->GetName(), -1, lpFilename, nSize);
                 break;
             }
         }
@@ -2354,9 +2358,9 @@ CreateProcessModules(
         const char *moduleName = rwpi.prp_vip.vip_path;
 
         bool dup = false;
-        for (ProcessModules *entry = listHead; entry != NULL; entry = entry->Next)
+        for (ProcessModules *entry = listHead; entry != NULL; entry = entry->GetNext())
         {
-            if (strcmp(moduleName, entry->Name) == 0)
+            if (strcmp(moduleName, entry->_name) == 0)
             {
                 dup = true;
                 break;
@@ -2374,9 +2378,10 @@ CreateProcessModules(
                 count = 0;
                 break; // no memory
             }
-            memcpy_s(entry->Name, cbModuleName, moduleName, cbModuleName);
-            entry->BaseAddress = (void *)rwpi.prp_prinfo.pri_address;
-            entry->Next = listHead;
+            memcpy_s(entry->_name, cbModuleName, moduleName, cbModuleName);
+            entry->_minimumAddress = 0;
+            entry->_baseAddress = (void *)rwpi.prp_prinfo.pri_address;
+            entry->_next = listHead;
             listHead = entry;
             count++;
         }
@@ -2426,15 +2431,20 @@ CreateProcessModules(
         int devHi, devLo, inode;
         char moduleName[PATH_MAX];
 
-        if (sscanf_s(line, "%p-%p %*[-rwxsp] %p %x:%x %d %s\n", &startAddress, &endAddress, &offset, &devHi, &devLo, &inode, moduleName, ARRAY_SIZE(moduleName)) == 7)
+        if (sscanf_s(line, "%p-%p %*[-rwxsp] %p %x:%x %d %[^\n]\n", &startAddress, &endAddress, &offset, &devHi, &devLo, &inode, moduleName, ARRAY_SIZE(moduleName)) == 7)
         {
             if (inode != 0)
             {
                 bool dup = false;
-                for (ProcessModules *entry = listHead; entry != NULL; entry = entry->Next)
+                for (ProcessModules *entry = listHead; entry != NULL; entry = entry->GetNext())
                 {
-                    if (strcmp(moduleName, entry->Name) == 0)
+                    if (strcmp(moduleName, entry->GetName()) == 0)
                     {
+                        if (entry->_baseAddress == 0 && offset == 0)
+                        {
+                            entry->_baseAddress = startAddress;
+                        }
+                        entry->_minimumAddress = std::min(startAddress, entry->_minimumAddress);
                         dup = true;
                         break;
                     }
@@ -2451,9 +2461,14 @@ CreateProcessModules(
                         count = 0;
                         break;
                     }
-                    strcpy_s(entry->Name, cbModuleName, moduleName);
-                    entry->BaseAddress = startAddress;
-                    entry->Next = listHead;
+                    strcpy_s(entry->_name, cbModuleName, moduleName);
+                    entry->_baseAddress = 0;
+                    entry->_minimumAddress = startAddress;
+                    if (offset == 0)
+                    {
+                        entry->_baseAddress = startAddress;
+                    }
+                    entry->_next = listHead;
                     listHead = entry;
                     count++;
                 }
@@ -2489,7 +2504,7 @@ DestroyProcessModules(IN ProcessModules *listHead)
 {
     for (ProcessModules *entry = listHead; entry != NULL; )
     {
-        ProcessModules *next = entry->Next;
+        ProcessModules *next = entry->GetNext();
         free(entry);
         entry = next;
     }
index 19b19eeb54534a37010ed77027623f4543f32f29..0d53d34f27f42e3eb51d1b435233d1d77f75b348 100644 (file)
@@ -79,7 +79,7 @@
   </PropertyGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <ClCompile>
-      <AdditionalIncludeDirectories>$(ArtifactsObjDir)\Windows_NT.x64.Debug\src\utilcode;$(RepoRoot)src\utilcode;$(ArtifactsObjDir);$(RepoRoot)src;$(RepoRoot)src\pal\prebuilt\inc;$(RepoRoot)src\inc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>$(RepoRoot)artifacts\obj;$(RepoRoot)src\shared;$(RepoRoot)src\shared\inc;$(RepoRoot)src\shared\pal\prebuilt\inc;$(RepoRoot)src\shared\pal\inc;$(RepoRoot)src\shared\pal\inc\rt;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <AdditionalOptions>%(AdditionalOptions) /guard:ehcont /Zm200 /Zc:strictStrings /w34092 /w34121 /w34125 /w34130 /w34132 /w34212 /w34530 /w35038 /w44177 /ZH:SHA_256 /source-charset:utf-8 /homeparams</AdditionalOptions>
       <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
       <WarningLevel>Level3</WarningLevel>
       <PreprocessorDefinitions>WIN32;_WINDOWS;_CRTIMP=;FEATURE_UTILCODE_NO_DEPENDENCIES;SELF_NO_HOST;DEBUG;_DEBUG;_DBG;URTBLDENV_FRIENDLY=Debug;BUILDENV_DEBUG=1;HOST_AMD64;HOST_64BIT;HOST_WINDOWS;_FILE_OFFSET_BITS=64;TARGET_AMD64;TARGET_64BIT;TARGET_WINDOWS;_AMD64_;_WIN64;AMD64;BIT64=1;_TARGET_64BIT_=1;_TARGET_AMD64_=1;DBG_TARGET_64BIT=1;DBG_TARGET_AMD64=1;DBG_TARGET_WIN64=1;_WIN32;WINVER=0x0602;_WIN32_WINNT=0x0602;WIN32_LEAN_AND_MEAN=1;_CRT_SECURE_NO_WARNINGS;UNICODE;_UNICODE;FEATURE_COMINTEROP;FEATURE_HIJACK;_BLD_CLR;CMAKE_INTDIR="Debug";%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <ObjectFileName>$(IntDir)</ObjectFileName>
+      <BrowseInformation>true</BrowseInformation>
     </ClCompile>
     <ResourceCompile>
       <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_CRTIMP=;FEATURE_UTILCODE_NO_DEPENDENCIES;SELF_NO_HOST;DEBUG;_DBG;URTBLDENV_FRIENDLY=Debug;BUILDENV_DEBUG=1;HOST_AMD64;HOST_64BIT;HOST_WINDOWS;_FILE_OFFSET_BITS=64;TARGET_AMD64;TARGET_64BIT;TARGET_WINDOWS;_AMD64_;_WIN64;AMD64;BIT64=1;_TARGET_64BIT_=1;_TARGET_AMD64_=1;DBG_TARGET_64BIT=1;DBG_TARGET_AMD64=1;DBG_TARGET_WIN64=1;_WIN32;WINVER=0x0602;_WIN32_WINNT=0x0602;WIN32_LEAN_AND_MEAN=1;_CRT_SECURE_NO_WARNINGS;UNICODE;_UNICODE;;FEATURE_COMINTEROP;FEATURE_HIJACK;_BLD_CLR;CMAKE_INTDIR=\"Debug\";%(PreprocessorDefinitions)</PreprocessorDefinitions>
     <Lib>
       <AdditionalOptions>%(AdditionalOptions) /machine:x64 /IGNORE:4221</AdditionalOptions>
     </Lib>
+    <Bscmake>
+      <PreserveSbr>true</PreserveSbr>
+    </Bscmake>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Checked|x64'">
     <ClCompile>
   <ImportGroup Label="ExtensionTargets">
     <Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
   </ImportGroup>
-</Project>
+</Project>
\ No newline at end of file
diff --git a/src/tests/DbgShim.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt b/src/tests/DbgShim.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt
new file mode 100644 (file)
index 0000000..e03b297
--- /dev/null
@@ -0,0 +1,113 @@
+<Configuration>
+  <Import ConfigFile="Debugger.Tests.Common.txt" />
+  <DotNetRoot>$(RepoRootDir)/.dotnet-test</DotNetRoot>
+  <Import ConfigFile="$(DotNetRoot)/Debugger.Tests.Versions.txt" />
+
+  <RootBinDir>$(RepoRootDir)/artifacts</RootBinDir>
+  <InstallDir>$(RootBinDir)/bin/$(OS).$(TargetArchitecture).$(TargetConfiguration)</InstallDir>
+  <LogDir>$(RootBinDir)/TestResults/$(TargetConfiguration)/dbgshim.unittests_$(Timestamp)</LogDir>
+  <!-- Build the debuggee for this framework version but run it on latest -->
+  <BuildProjectFrameworkLatest Condition="StartsWith('$(RuntimeVersionLatest)', '6')">net6.0</BuildProjectFrameworkLatest>
+
+  <TestProduct>ProjectK</TestProduct>
+  <DebuggeeSourceRoot>$(RepoRootDir)\src\tests\DbgShim.UnitTests\Debuggees</DebuggeeSourceRoot>
+  <DebuggeeBuildProcess>cli</DebuggeeBuildProcess>
+  <DebuggeeName>SimpleDebuggee</DebuggeeName>
+
+  !-- Use the global.json SDK to build and the test SDK/runtime to run -->
+  <!--
+  <CliPath>$(RepoRootDir)/.dotnet/dotnet</CliPath>
+  -->
+  <CliPath>$(DotNetRoot)/dotnet</CliPath>
+
+  <NuGetPackageFeeds>
+      dotnet6=https://dnceng.pkgs.visualstudio.com/public/_packaging/dotnet6/nuget/v3/index.json;
+      dotnet-core=https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json;
+      dotnet-public=https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json
+  </NuGetPackageFeeds>
+
+  <!-- Run OpenVirtualProcess tests on Linux -->
+  <RunTestsLinux_x64>false</RunTestsLinux_x64>
+  <RunTestsLinux_x64 Condition="'$(OS)' == 'Linux'">true</RunTestsLinux_x64>
+  <RunTestsLinux_x64 Condition="'$(TargetArchitecture)' != 'x64'">false</RunTestsLinux_x64>
+  
+  <RunTestsLinux_arm64>false</RunTestsLinux_arm64>
+  <RunTestsLinux_arm64 Condition="'$(OS)' == 'Linux'">true</RunTestsLinux_arm64>
+  <RunTestsLinux_arm64 Condition="'$(TargetArchitecture)' != 'arm64'">false</RunTestsLinux_arm64>
+
+  <Options>
+    <!-- Run OpenVirtualProcess tests on Linux -->
+    <Option>
+      <TestName>OpenVirtualProcess</TestName>
+      <RuntimeFrameworkVersion>$(RuntimeVersionLatest)</RuntimeFrameworkVersion>
+
+      <!-- Run OpenVirtualProcess tests on Linux x64 -->
+      <Options Condition="'$(RunTestsLinux_x64)' == 'true'">
+        <Option>
+          <DumpFile>$(Package_TestAssets_Linux_x64_6_0)/WebApp3/SOS.WebApp3.Heap.dmp</DumpFile>
+          <TestDataFile>$(Package_TestAssets_Linux_x64_6_0)/WebApp3/SOS.WebApp3.Heap.dmp.xml</TestDataFile>
+        </Option>
+      </Options>
+
+      <!-- Run OpenVirtualProcess tests on Linux arm64 -->
+      <Options Condition="'$(RunTestsLinux_arm64)' == 'true'">
+        <Option>
+          <DumpFile>$(Package_TestAssets_Linux_arm64_6_0)/WebApp3/SOS.WebApp3.Heap.dmp</DumpFile>
+          <TestDataFile>$(Package_TestAssets_Linux_arm64_6_0)/WebApp3/SOS.WebApp3.Heap.dmp.xml</TestDataFile>
+        </Option>
+      </Options>
+    </Option>
+
+    <!-- Run the rest of the tests on Linux/MacOS for all architectures -->
+    <Option>
+      <Options>
+        <!--
+            Single file
+         -->
+        <Option Condition="'$(RuntimeVersionLatest)' != ''">
+          <DebuggeeBuildRoot>$(RootBinDir)/Debuggees/SingleFile</DebuggeeBuildRoot>
+          <BuildProjectFramework>$(BuildProjectFrameworkLatest)</BuildProjectFramework>
+          <PublishSingleFile>true</PublishSingleFile>
+          <BuildProjectRuntime>$(TargetRid)</BuildProjectRuntime>
+          <RuntimeFrameworkVersion>$(SingleFileRuntimeVersion)</RuntimeFrameworkVersion>
+        </Option>
+        <!--
+            Default
+         -->
+        <Option Condition="'$(RuntimeVersionLatest)' != ''">
+          <DebuggeeBuildRoot>$(RootBinDir)/Debuggees</DebuggeeBuildRoot>
+          <BuildProjectFramework>$(BuildProjectFrameworkLatest)</BuildProjectFramework>
+          <RuntimeFrameworkVersion>$(RuntimeVersionLatest)</RuntimeFrameworkVersion>
+        </Option>
+      </Options>
+    </Option>
+  </Options>
+
+  <!-- Single-file debuggees don't need the host -->
+  <HostExe Condition="'$(PublishSingleFile)' != 'true'">$(DotNetRoot)/dotnet</HostExe>
+  <HostArgs Condition="'$(PublishSingleFile)' != 'true'">--fx-version $(RuntimeFrameworkVersion)</HostArgs>
+
+  <RuntimeModuleDir>$(DotNetRoot)/shared/Microsoft.NETCore.App/$(RuntimeFrameworkVersion)</RuntimeModuleDir>
+
+  <Options>
+    <Option Condition="'$(OS)' == 'Linux'">
+      <!--
+      <DbgShimPath>$(DotNetRoot)/shared/Microsoft.NETCore.App/$(RuntimeFrameworkVersion)/libdbgshim.so</DbgShimPath>
+       -->
+      <DbgShimPath>$(InstallDir)/libdbgshim.so</DbgShimPath>
+      <RuntimeModulePath Condition="'$(RuntimeModuleDir)' != ''">$(RuntimeModuleDir)/libcoreclr.so</RuntimeModulePath>
+      <DbiModulePath Condition="'$(RuntimeModuleDir)' != ''">$(RuntimeModuleDir)/libmscordbi.so</DbiModulePath>
+      <DacModulePath Condition="'$(RuntimeModuleDir)' != ''">$(RuntimeModuleDir)/libmscordaccore.so</DacModulePath>
+    </Option>
+    <Option Condition="'$(OS)' == 'OSX'">
+      <!--
+      <DbgShimPath>$(DotNetRoot)/shared/Microsoft.NETCore.App/$(RuntimeFrameworkVersion)/libdbgshim.dylib</DbgShimPath>
+       -->
+      <DbgShimPath>$(InstallDir)/libdbgshim.dylib</DbgShimPath>
+      <RuntimeModulePath Condition="'$(RuntimeModuleDir)' != ''">$(RuntimeModuleDir)/libcoreclr.dylib</RuntimeModulePath>
+      <DbiModulePath Condition="'$(RuntimeModuleDir)' != ''">$(RuntimeModuleDir)/libmscordbi.dylib</DbiModulePath>
+      <DacModulePath Condition="'$(RuntimeModuleDir)' != ''">$(RuntimeModuleDir)/libmscordaccore.dylib</DacModulePath>
+    </Option>
+  </Options>
+</Configuration>
diff --git a/src/tests/DbgShim.UnitTests/ConfigFiles/Windows/Debugger.Tests.Config.txt b/src/tests/DbgShim.UnitTests/ConfigFiles/Windows/Debugger.Tests.Config.txt
new file mode 100644 (file)
index 0000000..6f23a07
--- /dev/null
@@ -0,0 +1,98 @@
+<Configuration>
+  <Import ConfigFile="Debugger.Tests.Common.txt" />
+  <DotNetRoot Condition="'$(TargetArchitecture)' != 'x86'">$(RepoRootDir)\.dotnet-test</DotNetRoot>
+  <DotNetRoot Condition="'$(TargetArchitecture)' == 'x86'">$(RepoRootDir)\.dotnet-test\x86</DotNetRoot>
+  <Import ConfigFile="$(DotNetRoot)\Debugger.Tests.Versions.txt" />
+
+  <RootBinDir>$(RepoRootDir)\artifacts</RootBinDir>
+  <InstallDir>$(RootBinDir)\bin\Windows_NT.$(TargetArchitecture).$(TargetConfiguration)</InstallDir>
+  <LogDir>$(RootBinDir)\TestResults\$(TargetConfiguration)\dbgshim.unittests_$(Timestamp)</LogDir>
+
+  <!-- Build the debuggee for this framework version but run it on latest -->
+  <BuildProjectFrameworkLatest Condition="StartsWith('$(RuntimeVersionLatest)', '6')">net6.0</BuildProjectFrameworkLatest>
+
+  <TestProduct>ProjectK</TestProduct>
+  <DebuggeeSourceRoot>$(RepoRootDir)\src\tests\DbgShim.UnitTests\Debuggees</DebuggeeSourceRoot>
+  <DebuggeeBuildProcess>cli</DebuggeeBuildProcess>
+  <DebuggeeName>SimpleDebuggee</DebuggeeName>
+
+  <CliPath>$(DotNetRoot)\dotnet.exe</CliPath>
+
+  <NuGetPackageFeeds>
+      dotnet6=https://dnceng.pkgs.visualstudio.com/public/_packaging/dotnet6/nuget/v3/index.json;
+      dotnet-core=https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json;
+      dotnet-public=https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json
+  </NuGetPackageFeeds>
+
+  <RunTestsWindows_x64>true</RunTestsWindows_x64>
+  <RunTestsWindows_x64 Condition="'$(TargetArchitecture)' != 'x64'">false</RunTestsWindows_x64>
+  
+  <RunTestsWindows_x86>true</RunTestsWindows_x86>
+  <RunTestsWindows_x86 Condition="'$(TargetArchitecture)' != 'x86'">false</RunTestsWindows_x86>
+
+  <Options>
+    <!-- Run OpenVirtualProcess tests on Windows -->
+    <Option>
+      <TestName>OpenVirtualProcess</TestName>
+      <RuntimeFrameworkVersion>$(RuntimeVersionLatest)</RuntimeFrameworkVersion>
+
+      <!-- Run OpenVirtualProcess tests on Windows x64 -->
+      <Options Condition="'$(RunTestsWindows_x64)' == 'true'">
+        <Option>
+          <DumpFile>$(Package_TestAssets_Windows_x64_6_0)\DivZero\SOS.DivZero.Heap.dmp</DumpFile>
+          <TestDataFile>$(Package_TestAssets_Windows_x64_6_0)\DivZero\SOS.DivZero.Heap.dmp.xml</TestDataFile>
+        </Option>
+        <Option>
+          <DumpFile>$(Package_TestAssets_Linux_x64_6_0)\WebApp3\SOS.WebApp3.Heap.dmp</DumpFile>
+          <TestDataFile>$(Package_TestAssets_Linux_x64_6_0)\WebApp3\SOS.WebApp3.Heap.dmp.xml</TestDataFile>
+        </Option>
+       </Options>
+
+      <!-- Run OpenVirtualProcess tests on Windows x86 -->
+      <Options Condition="'$(RunTestsWindows_x86)' == 'true'">
+        <Option>
+          <DumpFile>$(Package_TestAssets_Windows_x86_6_0)\DivZero\SOS.DivZero.Heap.dmp</DumpFile>
+          <TestDataFile>$(Package_TestAssets_Windows_x86_6_0)\DivZero\SOS.DivZero.Heap.dmp.xml</TestDataFile>
+        </Option>
+       </Options>
+    </Option>
+
+    <!-- Run the rest of the tests on Windows for all architectures -->
+    <Option>
+      <Options>
+        <!--
+            Single file
+         -->
+        <Option Condition="'$(RuntimeVersionLatest)' != ''">
+          <DebuggeeBuildRoot>$(RootBinDir)\Debuggees\SingleFile</DebuggeeBuildRoot>
+          <BuildProjectFramework>$(BuildProjectFrameworkLatest)</BuildProjectFramework>
+          <PublishSingleFile>true</PublishSingleFile>
+          <BuildProjectRuntime>$(TargetRid)</BuildProjectRuntime>
+          <RuntimeFrameworkVersion>$(SingleFileRuntimeVersion)</RuntimeFrameworkVersion>
+        </Option>
+        <!--
+            Default
+          -->
+        <Option Condition="'$(RuntimeVersionLatest)' != ''">
+          <DebuggeeBuildRoot>$(RootBinDir)\Debuggees</DebuggeeBuildRoot>
+          <BuildProjectFramework>$(BuildProjectFrameworkLatest)</BuildProjectFramework>
+          <RuntimeFrameworkVersion>$(RuntimeVersionLatest)</RuntimeFrameworkVersion>
+        </Option>
+      </Options>
+    </Option>
+  </Options>
+
+  <!-- Single-file debuggees don't need the host -->
+  <HostExe Condition="'$(PublishSingleFile)' != 'true'">$(DotNetRoot)\dotnet.exe</HostExe>
+  <HostArgs Condition="'$(PublishSingleFile)' != 'true'">--fx-version $(RuntimeFrameworkVersion)</HostArgs>
+
+  <RuntimeModuleDir>$(DotNetRoot)\shared\Microsoft.NETCore.App\$(RuntimeFrameworkVersion)</RuntimeModuleDir>
+
+  <!--
+  <DbgShimPath>$(DotNetRoot)\shared\Microsoft.NETCore.App\$(RuntimeFrameworkVersion)\dbgshim.dll</DbgShimPath>
+   -->
+  <DbgShimPath>$(InstallDir)\dbgshim.dll</DbgShimPath>
+  <RuntimeModulePath Condition="'$(RuntimeModuleDir)' != ''">$(RuntimeModuleDir)\coreclr.dll</RuntimeModulePath>
+  <DbiModulePath Condition="'$(RuntimeModuleDir)' != ''">$(RuntimeModuleDir)\mscordbi.dll</DbiModulePath>
+  <DacModulePath Condition="'$(RuntimeModuleDir)' != ''">$(RuntimeModuleDir)\mscordaccore.dll</DacModulePath>
+</Configuration>
diff --git a/src/tests/DbgShim.UnitTests/DbgShim.UnitTests.csproj b/src/tests/DbgShim.UnitTests/DbgShim.UnitTests.csproj
new file mode 100644 (file)
index 0000000..fd0d206
--- /dev/null
@@ -0,0 +1,91 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net6.0</TargetFramework>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <DbgShimConfigFileName>$(OutputPath)$(TargetFramework)\Debugger.Tests.Common.txt</DbgShimConfigFileName>
+    <TestAssetsVersion>1.0.257801</TestAssetsVersion>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Compile Remove="Debuggees\SimpleDebuggee\SimpleDebuggee.cs" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.TestHelpers\Microsoft.Diagnostics.TestHelpers.csproj" />
+    <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\SOS\SOS.Hosting\SOS.Hosting.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Content Include="$(MSBuildThisFileDirectory)ConfigFiles\Windows\Debugger.Tests.Config.txt" Condition="'$(OS)' == 'Windows_NT'">
+      <Link>Debugger.Tests.Config.txt</Link>
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="$(MSBuildThisFileDirectory)ConfigFiles\Unix\Debugger.Tests.Config.txt" Condition="$(OS) == 'Unix'">
+      <Link>Debugger.Tests.Config.txt</Link>
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+    
+  <ItemGroup Condition="'$(OS)' == 'Windows_NT'">
+    <PackageDownload Include="TestAssets.Windows.x64.6.0" Version="[$(TestAssetsVersion)]" />
+    <PackageDownload Include="TestAssets.Windows.x86.6.0" Version="[$(TestAssetsVersion)]" />
+  </ItemGroup>
+       
+  <ItemGroup Condition="'$(OS)' == 'Unix' OR '$(OS)' == 'Windows_NT'">
+    <PackageDownload Include="TestAssets.Linux.x64.6.0" Version="[$(TestAssetsVersion)]" />
+    <PackageDownload Include="TestAssets.Linux.arm64.6.0" Version="[$(TestAssetsVersion)]" />
+  </ItemGroup>
+       
+  <!--
+    Windows test configuration file
+  -->
+  <ItemGroup Condition="'$(OS)' == 'Windows_NT'">
+    <ConfigFileEntries Include="Windows_NT">
+       <ConfigFileEntry>
+<![CDATA[
+<Configuration>
+  <TargetConfiguration>$(Configuration)</TargetConfiguration>
+  <RepoRootDir>$(RepoRoot)</RepoRootDir>
+  <Package_TestAssets_Windows_x64_6_0>$(NuGetPackageRoot)testassets.windows.x64.6.0\$(TestAssetsVersion)\content</Package_TestAssets_Windows_x64_6_0>
+  <Package_TestAssets_Windows_x86_6_0>$(NuGetPackageRoot)testassets.windows.x86.6.0\$(TestAssetsVersion)\content</Package_TestAssets_Windows_x86_6_0>
+  <Package_TestAssets_Linux_x64_6_0>$(NuGetPackageRoot)testassets.linux.x64.6.0\$(TestAssetsVersion)\content</Package_TestAssets_Linux_x64_6_0>
+  <Package_TestAssets_Linux_arm64_6_0>$(NuGetPackageRoot)testassets.linux.arm64.6.0\$(TestAssetsVersion)\content</Package_TestAssets_Linux_arm64_6_0>
+</Configuration>
+]]>
+       </ConfigFileEntry>
+    </ConfigFileEntries>
+  </ItemGroup>
+
+  <!--
+    Linux/MacOS test configuration file
+  -->
+  <ItemGroup Condition="'$(OS)' == 'Unix'">
+    <ConfigFileEntries Include="Unix">
+       <ConfigFileEntry>
+<![CDATA[
+<Configuration>
+  <TargetConfiguration>$(Configuration)</TargetConfiguration>
+  <RepoRootDir>$(RepoRoot)</RepoRootDir>
+  <Package_TestAssets_Linux_x64_6_0>$(NuGetPackageRoot)testassets.linux.x64.6.0/$(TestAssetsVersion)/content</Package_TestAssets_Linux_x64_6_0>
+  <Package_TestAssets_Linux_arm64_6_0>$(NuGetPackageRoot)testassets.linux.arm64.6.0/$(TestAssetsVersion)/content</Package_TestAssets_Linux_arm64_6_0>
+</Configuration>
+]]>
+       </ConfigFileEntry>
+    </ConfigFileEntries>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="Debuggees\SimpleDebuggee\SimpleDebuggee.cs" />
+  </ItemGroup>
+
+  <Target Name="DbgShimWriteTestConfigFile" Outputs="$(DbgShimConfigFileName)" AfterTargets="Build">
+     <PropertyGroup>
+       <TestConfigFileLines>@(ConfigFileEntries-&gt;Metadata("ConfigFileEntry"))</TestConfigFileLines>
+     </PropertyGroup>
+    <WriteLinesToFile File="$(DbgShimConfigFileName)" Lines="$(TestConfigFileLines)" Overwrite="true" WriteOnlyWhenDifferent="true" />
+    <Message Importance="High" Text="Created config file $(DbgShimConfigFileName)" />
+    <ItemGroup>
+      <FileWrites Include="$(DbgShimConfigFileName)" />
+    </ItemGroup>
+  </Target>
+</Project>
diff --git a/src/tests/DbgShim.UnitTests/DbgShimAPI.cs b/src/tests/DbgShim.UnitTests/DbgShimAPI.cs
new file mode 100644 (file)
index 0000000..1eba03f
--- /dev/null
@@ -0,0 +1,345 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Diagnostics.Runtime;
+using Microsoft.Diagnostics.Runtime.Utilities;
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace Microsoft.Diagnostics
+{
+    public class DbgShimAPI
+    {
+        private static bool _initialized = false;
+
+        private static CreateProcessForLaunchDelegate _createProcessForLaunch;
+        private static CloseResumeHandleDelegate _closeResumeHandle;
+        private static ResumeProcessDelegate _resumeProcess;
+
+        private static RegisterForRuntimeStartupDelegate _registerForRuntimeStartup;
+        private static RegisterForRuntimeStartupExDelegate _registerForRuntimeStartupEx;
+        private static RegisterForRuntimeStartup3Delegate _registerForRuntimeStartup3;
+        private static UnregisterForRuntimeStartupDelegate _unregisterForRuntimeStartup;
+
+        private static EnumerateCLRsDelegate _enumerateCLRs;
+        private static CloseCLREnumerationDelegate _closeCLREnumeration;
+        private static CreateVersionStringFromModuleDelegate _createVersionStringFromModule;
+
+        private static CreateDebuggingInterfaceFromVersionDelegate _createDebuggingInterfaceFromVersion;
+        private static CreateDebuggingInterfaceFromVersionExDelegate _createDebuggingInterfaceFromVersionEx;
+        private static CreateDebuggingInterfaceFromVersion2Delegate _createDebuggingInterfaceFromVersion2;
+        private static CreateDebuggingInterfaceFromVersion3Delegate _createDebuggingInterfaceFromVersion3;
+
+        private static CLRCreateInstanceDelegate _clrCreateInstance;
+
+        private static IntPtr _dbgshimModuleHandle = IntPtr.Zero;
+
+        public const int CorDebugVersion_2_0 = 3;
+        public const int CorDebugVersion_4_0 = 4;
+
+        public static void Initialize(string dbgshimPath)
+        {
+            // check if already initialized
+            if (_initialized)
+            {
+                return;
+            }
+            if (string.IsNullOrWhiteSpace(dbgshimPath) || !File.Exists(dbgshimPath))
+            {
+                throw new ArgumentException($"Dbgshim path not set or the dbgshim at '{dbgshimPath}' does not exists");
+            }
+            _dbgshimModuleHandle = DataTarget.PlatformFunctions.LoadLibrary(dbgshimPath); 
+            _createProcessForLaunch = GetDelegateFunction<CreateProcessForLaunchDelegate>("CreateProcessForLaunch");
+            _resumeProcess = GetDelegateFunction<ResumeProcessDelegate>("ResumeProcess");
+            _closeResumeHandle = GetDelegateFunction<CloseResumeHandleDelegate>("CloseResumeHandle");
+            _registerForRuntimeStartup = GetDelegateFunction<RegisterForRuntimeStartupDelegate>("RegisterForRuntimeStartup");
+            _registerForRuntimeStartupEx = GetDelegateFunction<RegisterForRuntimeStartupExDelegate>("RegisterForRuntimeStartupEx");
+            _registerForRuntimeStartup3 = GetDelegateFunction<RegisterForRuntimeStartup3Delegate>("RegisterForRuntimeStartup3", optional: true);
+            _unregisterForRuntimeStartup = GetDelegateFunction<UnregisterForRuntimeStartupDelegate>("UnregisterForRuntimeStartup");
+            _enumerateCLRs = GetDelegateFunction<EnumerateCLRsDelegate>("EnumerateCLRs");
+            _closeCLREnumeration = GetDelegateFunction<CloseCLREnumerationDelegate>("CloseCLREnumeration");
+            _createVersionStringFromModule = GetDelegateFunction<CreateVersionStringFromModuleDelegate>("CreateVersionStringFromModule");
+            _createDebuggingInterfaceFromVersion = GetDelegateFunction<CreateDebuggingInterfaceFromVersionDelegate>("CreateDebuggingInterfaceFromVersion");
+            _createDebuggingInterfaceFromVersionEx = GetDelegateFunction<CreateDebuggingInterfaceFromVersionExDelegate>("CreateDebuggingInterfaceFromVersionEx");
+            _createDebuggingInterfaceFromVersion2 = GetDelegateFunction<CreateDebuggingInterfaceFromVersion2Delegate>("CreateDebuggingInterfaceFromVersion2");
+            _createDebuggingInterfaceFromVersion3 = GetDelegateFunction<CreateDebuggingInterfaceFromVersion3Delegate>("CreateDebuggingInterfaceFromVersion3", optional: true);
+            _clrCreateInstance = GetDelegateFunction<CLRCreateInstanceDelegate>("CLRCreateInstance");
+            _initialized = true;
+        }
+
+        public static bool IsRegisterForRuntimeStartup3Supported => _registerForRuntimeStartup3 != default;
+
+        public static bool IsCreateDebuggingInterfaceFromVersion3Supported => _createDebuggingInterfaceFromVersion3 != default;
+
+        public static HResult CreateProcessForLaunch(string commandLine, bool suspendProcess, string currentDirectory, out int processId, out IntPtr resumeHandle)
+        {
+            return _createProcessForLaunch(commandLine, suspendProcess, lpEnvironment: IntPtr.Zero, currentDirectory, out processId, out resumeHandle);
+        }
+
+        public static HResult ResumeProcess(IntPtr handle) => _resumeProcess(handle);
+
+        public static HResult CloseResumeHandle(IntPtr handle) => _closeResumeHandle(handle);
+
+        public delegate void RuntimeStartupCallbackDelegate(ICorDebug cordbg, object parameter, HResult hresult);
+
+        public static HResult RegisterForRuntimeStartup(int pid, object parameter, out IntPtr unregisterToken, RuntimeStartupCallbackDelegate callback)
+        {
+            IntPtr nativeCallback = RuntimeStartupCallback(parameter, callback, out IntPtr nativeParameter);
+            return _registerForRuntimeStartup((uint)pid, nativeCallback, nativeParameter, out unregisterToken);
+        }
+
+        public static HResult RegisterForRuntimeStartupEx(int pid, string applicationGroupId, object parameter, out IntPtr unregisterToken, RuntimeStartupCallbackDelegate callback)
+        {
+            IntPtr nativeCallback = RuntimeStartupCallback(parameter, callback, out IntPtr nativeParameter);
+            return _registerForRuntimeStartupEx((uint)pid, applicationGroupId, nativeCallback, nativeParameter, out unregisterToken);
+        }
+
+        public static HResult RegisterForRuntimeStartup3(int pid, string applicationGroupId, object parameter, IntPtr libraryProvider, out IntPtr unregisterToken, RuntimeStartupCallbackDelegate callback)
+        {
+            if (_registerForRuntimeStartup3 == default)
+            {
+                throw new NotSupportedException("RegisterForRuntimeStartup3 not supported");
+            }
+            IntPtr nativeCallback = RuntimeStartupCallback(parameter, callback, out IntPtr nativeParameter);
+            return _registerForRuntimeStartup3((uint)pid, applicationGroupId, libraryProvider, nativeCallback, nativeParameter, out unregisterToken);
+        }
+
+        private delegate void NativeRuntimeStartupCallbackDelegate(IntPtr cordbg, IntPtr parameter, HResult hresult);
+
+        private static IntPtr RuntimeStartupCallback(object parameter, RuntimeStartupCallbackDelegate callback, out IntPtr nativeParameter)
+        {
+            NativeRuntimeStartupCallbackDelegate native = (IntPtr cordbg, IntPtr param, HResult hresult) => {
+                GCHandle gch = GCHandle.FromIntPtr(param);
+                callback(ICorDebug.Create(cordbg), gch.Target, hresult);
+                gch.Free();
+            };
+            GCHandle gchParameter = GCHandle.Alloc(parameter);
+            nativeParameter = GCHandle.ToIntPtr(gchParameter);
+            return Marshal.GetFunctionPointerForDelegate(native);
+        }
+
+        public static HResult UnregisterForRuntimeStartup(IntPtr unregisterToken) =>  _unregisterForRuntimeStartup(unregisterToken);
+
+        private const int HRESULT_ERROR_PARTIAL_COPY = unchecked((int)0x8007012b);
+        private const int HRESULT_ERROR_BAD_LENGTH = unchecked((int)0x80070018);
+
+        public static unsafe HResult EnumerateCLRs(int processId, Action<IntPtr[], string[]> callback)
+        {
+            HResult hr = HResult.S_OK;
+
+            int numRetries = 0;
+            while (numRetries < 25)
+            {
+                hr = _enumerateCLRs(processId, out IntPtr* handleArray, out char** stringArray, out int arrayLength);
+                if (hr == HResult.S_OK)
+                {
+                    IntPtr[] handles = new IntPtr[arrayLength];
+                    string[] moduleNames = new string[arrayLength];
+                    try
+                    {
+                        for (int i = 0; i < arrayLength; i++)
+                        {
+                            handles[i] = handleArray[i];
+                            moduleNames[i] = new string(stringArray[i]);
+                        }
+                        callback(handles, moduleNames);
+                    }
+                    finally
+                    {
+                        hr = _closeCLREnumeration(handleArray, stringArray, arrayLength);
+                    }
+                    break;
+                }
+                // EnumerateCLRs uses the OS API CreateToolhelp32Snapshot which can return ERROR_BAD_LENGTH or
+                // ERROR_PARTIAL_COPY. If we get either of those, we try wait 1/10th of a second try again (that
+                // is the recommendation of the OS API owners).
+                if ((hr != HRESULT_ERROR_PARTIAL_COPY) && (hr != HRESULT_ERROR_BAD_LENGTH))
+                {
+                    break;
+                }
+                Thread.Sleep(100);
+                numRetries++;
+            }
+            return hr;
+        }
+
+        private const int HRESULT_ERROR_INSUFFICIENT_BUFFER = unchecked((int)0x8007007a);
+
+        public static unsafe HResult CreateVersionStringFromModule(int processId, string modulePath, out string versionString)
+        {
+            versionString = null;
+            HResult hr = _createVersionStringFromModule(processId, modulePath, null, 0, out int versionStringSize);
+            if (hr == HRESULT_ERROR_INSUFFICIENT_BUFFER)
+            {
+                char[] versionBuffer = new char[versionStringSize];
+                fixed (char* versionBufferPtr = versionBuffer)
+                {
+                    hr = _createVersionStringFromModule(processId, modulePath, versionBufferPtr, versionStringSize, out versionStringSize);
+                    if (hr == 0)
+                    {
+                        versionString = new string(versionBuffer);
+                    }
+                }
+            }
+            return hr;
+        }
+
+        public static HResult CreateDebuggingInterfaceFromVersion(string versionString, out ICorDebug cordbg)
+        {
+            HResult hr = _createDebuggingInterfaceFromVersion(versionString, out IntPtr punk);
+            cordbg = ICorDebug.Create(punk);
+            return hr;
+        }
+
+        public static HResult CreateDebuggingInterfaceFromVersionEx(int debuggerVersion, string versionString, out ICorDebug cordbg)
+        {
+            HResult hr = _createDebuggingInterfaceFromVersionEx(debuggerVersion, versionString, out IntPtr punk);
+            cordbg = ICorDebug.Create(punk);
+            return hr;
+        }
+
+        public static HResult CreateDebuggingInterfaceFromVersion2(int debuggerVersion, string versionString, string applicationGroupId, out ICorDebug cordbg)
+        {
+            HResult hr = _createDebuggingInterfaceFromVersion2(debuggerVersion, versionString, applicationGroupId, out IntPtr punk);
+            cordbg = ICorDebug.Create(punk);
+            return hr;
+        }
+
+        public static HResult CreateDebuggingInterfaceFromVersion3(int debuggerVersion, string versionString, string applicationGroupId, IntPtr libraryProvider, out ICorDebug cordbg)
+        {
+            if (_createDebuggingInterfaceFromVersion3 == default)
+            {
+                throw new NotSupportedException("CreateDebuggingInterfaceFromVersion3 not supported");
+            }
+            HResult hr = _createDebuggingInterfaceFromVersion3(debuggerVersion, versionString, applicationGroupId, libraryProvider, out IntPtr punk);
+            cordbg = ICorDebug.Create(punk);
+            return hr;
+         }
+
+        public static HResult CLRCreateInstance(out ICLRDebugging clrDebugging)
+        {
+            HResult hr = _clrCreateInstance(ICLRDebugging.CLSID_ICLRDebugging, ICLRDebugging.IID_ICLRDebugging, out IntPtr punk);
+            clrDebugging = ICLRDebugging.Create(punk);
+            return hr;
+        }
+
+        private static T GetDelegateFunction<T>(string functionName, bool optional = false)
+            where T : Delegate
+        {
+            IntPtr functionAddress = DataTarget.PlatformFunctions.GetLibraryExport(_dbgshimModuleHandle, functionName);
+            if (functionAddress == IntPtr.Zero) {
+                if (optional)
+                {
+                    return default;
+                }
+                throw new ArgumentException($"Failed to get address of dbgshim!{functionName}");
+            }
+            return (T)Marshal.GetDelegateForFunctionPointer(functionAddress, typeof(T));
+        }
+
+        #region DbgShim pinvoke delegates
+
+        [UnmanagedFunctionPointer(CallingConvention.StdCall, SetLastError = true)]
+        private delegate HResult CreateProcessForLaunchDelegate(
+            [MarshalAs(UnmanagedType.LPWStr)] string lpCommandLine,
+            [MarshalAs(UnmanagedType.Bool)] bool bSuspendProcess,
+            IntPtr lpEnvironment,
+            [MarshalAs(UnmanagedType.LPWStr)] string lpCurrentDirectory,
+            out int processId,
+            out IntPtr suspendHandle);
+
+        [UnmanagedFunctionPointer(CallingConvention.StdCall, SetLastError = true)]
+        private delegate HResult ResumeProcessDelegate(
+            IntPtr handle);
+
+        [UnmanagedFunctionPointer(CallingConvention.StdCall, SetLastError = true)]
+        private delegate HResult CloseResumeHandleDelegate(
+            IntPtr handle);
+            
+        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
+        private delegate HResult RegisterForRuntimeStartupDelegate(
+            uint processId,
+            IntPtr callback,
+            IntPtr parameter,
+            out IntPtr unregisterToken);
+
+        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
+        private delegate HResult RegisterForRuntimeStartupExDelegate(
+            uint processId,
+            [MarshalAs(UnmanagedType.LPWStr)] string applicationGroupId,
+            IntPtr callback,
+            IntPtr parameter,
+            out IntPtr unregisterToken);
+
+        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
+        private delegate HResult RegisterForRuntimeStartup3Delegate(
+            uint processId,
+            [MarshalAs(UnmanagedType.LPWStr)] string applicationGroupId,
+            IntPtr libraryProvider,
+            IntPtr callback,
+            IntPtr parameter,
+            out IntPtr unregisterToken);
+
+        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
+        private delegate HResult UnregisterForRuntimeStartupDelegate(
+            IntPtr unregisterToken);
+
+        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
+        private unsafe delegate HResult EnumerateCLRsDelegate(
+            int processId,
+            out IntPtr* handleArray,
+            out char** stringArray,
+            out int arrayLength);
+
+        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
+        private unsafe delegate HResult CloseCLREnumerationDelegate(
+            IntPtr* handleArray,
+            char** stringArray,
+            int arrayLength);
+
+        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
+        private unsafe delegate HResult CreateVersionStringFromModuleDelegate(
+            int processId,
+            [MarshalAs(UnmanagedType.LPWStr)] string moduleName,
+            char* versionString,
+            int versionStringLength,
+            out int actualVersionStringLength);
+
+        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
+        private unsafe delegate HResult CreateDebuggingInterfaceFromVersionDelegate(
+            [MarshalAs(UnmanagedType.LPWStr)] string versionString,
+            out IntPtr cordbg);
+
+        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
+        private unsafe delegate HResult CreateDebuggingInterfaceFromVersionExDelegate(
+            int debuggerVersion,
+            [MarshalAs(UnmanagedType.LPWStr)] string versionString,
+            out IntPtr cordbg);
+
+        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
+        private unsafe delegate HResult CreateDebuggingInterfaceFromVersion2Delegate(
+            int debuggerVersion,
+            [MarshalAs(UnmanagedType.LPWStr)] string versionString,
+            [MarshalAs(UnmanagedType.LPWStr)] string applicationGroupId,
+            out IntPtr cordbg);
+
+        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
+        private unsafe delegate HResult CreateDebuggingInterfaceFromVersion3Delegate(
+            int debuggerVersion,
+            [MarshalAs(UnmanagedType.LPWStr)] string versionString,
+            [MarshalAs(UnmanagedType.LPWStr)] string applicationGroupId,
+            IntPtr libraryProvider,
+            out IntPtr cordbg);
+
+        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
+        private unsafe delegate HResult CLRCreateInstanceDelegate(
+            in Guid clrsid,
+            in Guid riid,
+            out IntPtr pInterface);
+
+        #endregion
+    }
+}
diff --git a/src/tests/DbgShim.UnitTests/DbgShimTests.cs b/src/tests/DbgShim.UnitTests/DbgShimTests.cs
new file mode 100644 (file)
index 0000000..9aed6ff
--- /dev/null
@@ -0,0 +1,596 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Diagnostics.DebugServices;
+using Microsoft.Diagnostics.Runtime.Utilities;
+using Microsoft.Diagnostics.TestHelpers;
+using SOS.Hosting;
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Abstractions;
+using Xunit.Extensions;
+
+namespace Microsoft.Diagnostics
+{
+    public class DbgShimTests : IDisposable
+    {
+        private const string ListenerName = "DbgShimTests";
+
+        public static IEnumerable<object[]> GetConfigurations(string key, string value)
+        {
+            return TestRunConfiguration.Instance.Configurations.Where((c) => key == null || c.AllSettings.GetValueOrDefault(key) == value).Select(c => new[] { c });
+        }
+
+        public static IEnumerable<object[]> Configurations => GetConfigurations("TestName", null);
+
+        private ITestOutputHelper Output { get; }
+
+        public DbgShimTests(ITestOutputHelper output)
+        {
+            Output = output;
+            LoggingListener.EnableListener(output, ListenerName);
+        }
+
+        void IDisposable.Dispose() => Trace.Listeners.Remove(ListenerName);
+
+        /// <summary>
+        /// Test RegisterForRuntimeStartup for launch
+        /// </summary>
+        [SkippableTheory, MemberData(nameof(Configurations))]
+        public async Task Launch1(TestConfiguration config)
+        {
+            await RemoteInvoke(config, nameof(Launch1), static async (string configXml) =>
+            {
+                using DebuggeeInfo debuggeeInfo = await StartDebuggee(configXml, launch: true);
+                TestRegisterForRuntimeStartup(debuggeeInfo, 1);
+
+                // Once the debuggee is resumed now wait until it starts
+                Assert.True(await debuggeeInfo.WaitForDebuggee());
+                return 0;
+            });
+        }
+
+        /// <summary>
+        /// Test RegisterForRuntimeStartupEx for launch
+        /// </summary>
+        [SkippableTheory, MemberData(nameof(Configurations))]
+        public async Task Launch2(TestConfiguration config)
+        {
+            await RemoteInvoke(config, nameof(Launch2), static async (string configXml) =>
+            {
+                using DebuggeeInfo debuggeeInfo = await StartDebuggee(configXml, launch: true);
+                TestRegisterForRuntimeStartup(debuggeeInfo, 2);
+
+                // Once the debuggee is resumed now wait until it starts
+                Assert.True(await debuggeeInfo.WaitForDebuggee());
+                return 0;
+            });
+        }
+
+        /// <summary>
+        /// Test RegisterForRuntimeStartup3 for launch
+        /// </summary>
+        [SkippableTheory, MemberData(nameof(Configurations))]
+        public async Task Launch3(TestConfiguration config)
+        {
+            if (OS.Kind == OSKind.OSX && config.PublishSingleFile)
+            {
+                throw new SkipTestException("Launch3 single-file on MacOS");
+            }
+            DbgShimAPI.Initialize(config.DbgShimPath());
+            if (!DbgShimAPI.IsRegisterForRuntimeStartup3Supported)
+            {
+                throw new SkipTestException("IsRegisterForRuntimeStartup3 not supported");
+            }
+            await RemoteInvoke(config, nameof(Launch3), static async (string configXml) => 
+            {
+                using DebuggeeInfo debuggeeInfo = await StartDebuggee(configXml, launch: true);
+                TestRegisterForRuntimeStartup(debuggeeInfo, 3);
+
+                // Once the debuggee is resumed now wait until it starts
+                Assert.True(await debuggeeInfo.WaitForDebuggee());
+                return 0;
+            });
+        }
+
+        /// <summary>
+        /// Test RegisterForRuntimeStartup for attach 
+        /// </summary>
+        [SkippableTheory, MemberData(nameof(Configurations))]
+        public async Task Attach1(TestConfiguration config)
+        {
+            await RemoteInvoke(config, nameof(Attach1), static async (string configXml) => 
+            {
+                using DebuggeeInfo debuggeeInfo = await StartDebuggee(configXml, launch: false);
+                TestRegisterForRuntimeStartup(debuggeeInfo, 1);
+                return 0;
+            });
+        }
+
+        /// <summary>
+        /// Test RegisterForRuntimeStartupEx for attach 
+        /// </summary>
+        [SkippableTheory, MemberData(nameof(Configurations))]
+        public async Task Attach2(TestConfiguration config)
+        {
+            await RemoteInvoke(config, nameof(Attach2), static async (string configXml) => 
+            {
+                using DebuggeeInfo debuggeeInfo = await StartDebuggee(configXml, launch: false);
+                TestRegisterForRuntimeStartup(debuggeeInfo, 2);
+                return 0;
+            });
+        }
+
+        /// <summary>
+        /// Test RegisterForRuntimeStartup3 for attach
+        /// </summary>
+        [SkippableTheory, MemberData(nameof(Configurations))]
+        public async Task Attach3(TestConfiguration config)
+        {
+            if (OS.Kind == OSKind.OSX && config.PublishSingleFile)
+            {
+                throw new SkipTestException("Attach3 single-file on MacOS");
+            }
+            DbgShimAPI.Initialize(config.DbgShimPath());
+            if (!DbgShimAPI.IsRegisterForRuntimeStartup3Supported)
+            {
+                throw new SkipTestException("IsRegisterForRuntimeStartup3 not supported");
+            }
+            await RemoteInvoke(config, nameof(Attach3), static async (string configXml) => 
+            {
+                using DebuggeeInfo debuggeeInfo = await StartDebuggee(configXml, launch: false);
+                TestRegisterForRuntimeStartup(debuggeeInfo, 3);
+                return 0;
+            });
+        }
+
+        /// <summary>
+        /// Test EnumerateCLRs/CloseCLREnumeration
+        /// </summary>
+        [SkippableTheory, MemberData(nameof(Configurations))]
+        public async Task EnumerateCLRs(TestConfiguration config)
+        {
+            await RemoteInvoke(config, nameof(EnumerateCLRs), static async (string configXml) =>
+            {
+                using DebuggeeInfo debuggeeInfo = await StartDebuggee(configXml, launch: false);
+                Trace.TraceInformation("EnumerateCLRs pid {0} START", debuggeeInfo.ProcessId);
+                HResult hr = DbgShimAPI.EnumerateCLRs(debuggeeInfo.ProcessId, (IntPtr[] continueEventHandles, string[] moduleNames) =>
+                {
+                    Assert.Single(continueEventHandles);
+                    Assert.Single(moduleNames);
+                    for (int i = 0; i < continueEventHandles.Length; i++)
+                    {
+                        Trace.TraceInformation("EnumerateCLRs pid {0} {1:X16} {2}", debuggeeInfo.ProcessId, continueEventHandles[i].ToInt64(), moduleNames[i]);
+                        AssertX.FileExists("ModuleFilePath", moduleNames[i], debuggeeInfo.Output);
+                    }
+                });
+                AssertResult(hr);
+                Trace.TraceInformation("EnumerateCLRs pid {0} DONE", debuggeeInfo.ProcessId);
+                return 0;
+            });
+        }
+
+        /// <summary>
+        /// Test CreateVersionStringFromModule/CreateDebuggingInterfaceFromVersion
+        /// </summary>
+        [SkippableTheory, MemberData(nameof(Configurations))]
+        public async Task CreateDebuggingInterfaceFromVersion(TestConfiguration config)
+        {
+            await RemoteInvoke(config, nameof(CreateDebuggingInterfaceFromVersion), static async (string configXml) =>
+            {
+                using DebuggeeInfo debuggeeInfo = await StartDebuggee(configXml, launch: false);
+                TestCreateDebuggingInterface(debuggeeInfo, 0);
+                return 0;
+            });
+        }
+
+        /// <summary>
+        /// Test CreateVersionStringFromModule/CreateDebuggingInterfaceFromVersionEx
+        /// </summary>
+        [SkippableTheory, MemberData(nameof(Configurations))]
+        public async Task CreateDebuggingInterfaceFromVersionEx(TestConfiguration config)
+        {
+            await RemoteInvoke(config, nameof(CreateDebuggingInterfaceFromVersionEx), static async (string configXml) =>
+            {
+                using DebuggeeInfo debuggeeInfo = await StartDebuggee(configXml, launch: false);
+                TestCreateDebuggingInterface(debuggeeInfo, 1);
+                return 0;
+            });
+        }
+
+        /// <summary>
+        /// Test CreateVersionStringFromModule/CreateDebuggingInterfaceFromVersion2
+        /// </summary>
+        [SkippableTheory, MemberData(nameof(Configurations))]
+        public async Task CreateDebuggingInterfaceFromVersion2(TestConfiguration config)
+        {
+            await RemoteInvoke(config, nameof(CreateDebuggingInterfaceFromVersion2), static async (string configXml) =>
+            {
+                using DebuggeeInfo debuggeeInfo = await StartDebuggee(configXml, launch: false);
+                TestCreateDebuggingInterface(debuggeeInfo, 2);
+                return 0;
+            });
+        }
+
+        /// <summary>
+        /// Test CreateVersionStringFromModule/CreateDebuggingInterfaceFromVersion3
+        /// </summary>
+        [SkippableTheory, MemberData(nameof(Configurations))]
+        public async Task CreateDebuggingInterfaceFromVersion3(TestConfiguration config)
+        {
+            if (OS.Kind == OSKind.OSX && config.PublishSingleFile)
+            {
+                throw new SkipTestException("CreateDebuggingInterfaceFromVersion3 single-file on MacOS");
+            }
+            DbgShimAPI.Initialize(config.DbgShimPath());
+            if (!DbgShimAPI.IsCreateDebuggingInterfaceFromVersion3Supported)
+            {
+                throw new SkipTestException("CreateDebuggingInterfaceFromVersion3 not supported");
+            }
+            await RemoteInvoke(config, nameof(CreateDebuggingInterfaceFromVersion3), static async (string configXml) =>
+            {
+                using DebuggeeInfo debuggeeInfo = await StartDebuggee(configXml, launch: false);
+                TestCreateDebuggingInterface(debuggeeInfo, 3);
+                return 0;
+            });
+        }
+
+        [SkippableTheory, MemberData(nameof(GetConfigurations), "TestName", "OpenVirtualProcess")]
+        public async Task OpenVirtualProcess(TestConfiguration config)
+        {
+            // The current Linux test assets are not alpine/musl
+            if (OS.IsAlpine)
+            {
+                throw new SkipTestException("Not supported on Alpine Linux (musl)");
+            }
+            if (!config.AllSettings.ContainsKey("DumpFile"))
+            {
+                throw new SkipTestException("OpenVirtualProcessTest: No dump file");
+            }
+            await RemoteInvoke(config, nameof(OpenVirtualProcess), static (string configXml) =>
+            {
+                AfterInvoke(configXml, out TestConfiguration cfg, out ITestOutputHelper output);
+
+                DbgShimAPI.Initialize(cfg.DbgShimPath());
+                AssertResult(DbgShimAPI.CLRCreateInstance(out ICLRDebugging clrDebugging));
+                Assert.NotNull(clrDebugging);
+
+                TestDump testDump = new(cfg);
+                ITarget target = testDump.Target;
+                IRuntimeService runtimeService = target.Services.GetService<IRuntimeService>();
+                IRuntime runtime = runtimeService.EnumerateRuntimes().Single();
+
+                CorDebugDataTargetWrapper dataTarget = new(target.Services);
+                LibraryProviderWrapper libraryProvider = new(target.OperatingSystem, runtime.RuntimeModule.BuildId, runtime.GetDbiFilePath(), runtime.GetDacFilePath());
+                ClrDebuggingVersion maxDebuggerSupportedVersion = new()
+                {
+                    StructVersion = 0,
+                    Major = 4,
+                    Minor = 0,
+                    Build = 0,
+                    Revision = 0,
+                };
+                HResult hr = clrDebugging.OpenVirtualProcess(
+                    runtime.RuntimeModule.ImageBase,
+                    dataTarget.ICorDebugDataTarget,
+                    libraryProvider.ILibraryProvider,
+                    maxDebuggerSupportedVersion,
+                    in RuntimeWrapper.IID_ICorDebugProcess,
+                    out IntPtr corDebugProcess,
+                    out ClrDebuggingVersion version,
+                    out ClrDebuggingProcessFlags flags);
+
+                AssertResult(hr);
+                Assert.NotEqual(IntPtr.Zero, corDebugProcess);
+                Assert.Equal(1, COMHelper.Release(corDebugProcess));
+                Assert.Equal(0, COMHelper.Release(corDebugProcess));
+                Assert.Equal(0, clrDebugging.Release());
+                return Task.FromResult(0);
+            });
+        }
+
+        #region Helper functions
+
+        private static async Task<DebuggeeInfo> StartDebuggee(string configXml, bool launch)
+        {
+            AfterInvoke(configXml, out TestConfiguration config, out ITestOutputHelper output);
+
+            DebuggeeInfo debuggeeInfo = new(output, config, launch);
+            string debuggeeName = config.DebuggeeName();
+
+            Assert.NotNull(debuggeeName);
+            Assert.NotNull(config.DbgShimPath());
+
+            DbgShimAPI.Initialize(config.DbgShimPath());
+
+            // Restore and build the debuggee
+            DebuggeeConfiguration debuggeeConfig = await DebuggeeCompiler.Execute(config, debuggeeName, debuggeeInfo.Output);
+
+            // Build the debuggee command line
+            StringBuilder commandLine = new();
+
+            // Get the full launch command line (includes the host if required)
+            if (!string.IsNullOrWhiteSpace(config.HostExe))
+            {
+                commandLine.Append(config.HostExe);
+                commandLine.Append(" ");
+                if (!string.IsNullOrWhiteSpace(config.HostArgs))
+                {
+                    commandLine.Append(config.HostArgs);
+                    commandLine.Append(" ");
+                }
+            }
+            commandLine.Append(debuggeeConfig.BinaryExePath);
+            commandLine.Append(" ");
+            commandLine.Append(debuggeeInfo.PipeName);
+
+            Trace.TraceInformation("CreateProcessForLaunch {0} {1} {2}", launch, commandLine.ToString(), debuggeeInfo.PipeName);
+            AssertResult(DbgShimAPI.CreateProcessForLaunch(commandLine.ToString(), launch, currentDirectory: null, out int processId, out IntPtr resumeHandle));
+            Assert.NotEqual(IntPtr.Zero, resumeHandle);
+            Trace.TraceInformation("CreateProcessForLaunch pid {0} {1}", processId, commandLine.ToString());
+
+            debuggeeInfo.ResumeHandle = resumeHandle;
+            debuggeeInfo.SetProcessId(processId);
+
+            // Wait for debuggee to start if attach/run
+            if (!launch)
+            {
+                Assert.True(await debuggeeInfo.WaitForDebuggee());
+            }
+            Trace.TraceInformation("CreateProcessForLaunch pid {0} DONE", processId);
+            return debuggeeInfo;
+        }
+
+        private static void TestRegisterForRuntimeStartup(DebuggeeInfo debuggeeInfo, int api)
+        {
+            TestConfiguration config = debuggeeInfo.TestConfiguration;
+            AutoResetEvent wait = new AutoResetEvent(false);
+            string applicationGroupId =  null;
+            IntPtr unregisterToken = IntPtr.Zero;
+            HResult result = HResult.S_OK;
+            HResult callbackResult = HResult.S_OK;
+            Exception callbackException = null;
+            ICorDebug corDebug = null;
+
+            Trace.TraceInformation("RegisterForRuntimeStartup pid {0} launch {1} api {2} START", debuggeeInfo.ProcessId, debuggeeInfo.Launch, api);
+
+            DbgShimAPI.RuntimeStartupCallbackDelegate callback = (ICorDebug cordbg, object parameter, HResult hr) => {
+                Trace.TraceInformation("RegisterForRuntimeStartup in callback pid {0} hr {1:X}", debuggeeInfo.ProcessId, hr);
+                corDebug = cordbg;
+                callbackResult = hr;
+                try
+                {
+                    // Only check the ICorDebug instance if success
+                    if (hr)
+                    {
+                        TestICorDebug(debuggeeInfo, cordbg);
+                    }
+                }
+                catch (Exception ex)
+                {
+                    Trace.TraceError(ex.ToString());
+                    callbackException = ex;
+                }
+                wait.Set();
+            };
+
+            switch (api)
+            {
+                case 1:
+                    result = DbgShimAPI.RegisterForRuntimeStartup(debuggeeInfo.ProcessId, parameter: IntPtr.Zero, out unregisterToken, callback);
+                    break;
+                case 2:
+                    result = DbgShimAPI.RegisterForRuntimeStartupEx(debuggeeInfo.ProcessId, applicationGroupId, parameter: IntPtr.Zero, out unregisterToken, callback);
+                    break;
+                case 3:
+                    LibraryProviderWrapper libraryProvider = new(config.RuntimeModulePath(), config.DbiModulePath(), config.DacModulePath());
+                    result = DbgShimAPI.RegisterForRuntimeStartup3(debuggeeInfo.ProcessId, applicationGroupId, parameter: IntPtr.Zero, libraryProvider.ILibraryProvider, out unregisterToken, callback);
+                    break;
+                default:
+                    throw new ArgumentException(nameof(api));
+            }
+
+            if (debuggeeInfo.Launch) 
+            {
+                AssertResult(debuggeeInfo.ResumeDebuggee());
+            }
+
+            AssertResult(result);
+
+            Trace.TraceInformation("RegisterForRuntimeStartup pid {0} waiting for callback", debuggeeInfo.ProcessId);
+            Assert.True(wait.WaitOne());
+            Trace.TraceInformation("RegisterForRuntimeStartup pid {0} after callback wait", debuggeeInfo.ProcessId);
+            
+            AssertResult(DbgShimAPI.UnregisterForRuntimeStartup(unregisterToken));
+            Assert.Null(callbackException);
+
+            switch (api)
+            {
+                case 1:
+                case 2:
+                    // The old APIs fail on single file apps
+                    Assert.Equal(!debuggeeInfo.TestConfiguration.PublishSingleFile, callbackResult);
+                    break;
+                case 3:
+                    // The new API should always succeed
+                    AssertResult(callbackResult);
+                    break;
+            }
+
+            if (callbackResult)
+            {
+                AssertResult(debuggeeInfo.WaitForCreateProcess());
+                Assert.Equal(0, corDebug.Release());
+            }
+            else
+            {
+                debuggeeInfo.Kill();
+            }
+
+            Trace.TraceInformation("RegisterForRuntimeStartup pid {0} DONE", debuggeeInfo.ProcessId);
+        }
+
+        private static void TestCreateDebuggingInterface(DebuggeeInfo debuggeeInfo, int api)
+        {
+            Trace.TraceInformation("TestCreateDebuggingInterface pid {0} api {1} START", debuggeeInfo.ProcessId, api);
+            HResult hr = DbgShimAPI.EnumerateCLRs(debuggeeInfo.ProcessId, (IntPtr[] continueEventHandles, string[] moduleNames) =>
+            {
+                TestConfiguration config = debuggeeInfo.TestConfiguration;
+                Assert.Single(continueEventHandles);
+                Assert.Single(moduleNames);
+                for (int i = 0; i < continueEventHandles.Length; i++)
+                {
+                    Trace.TraceInformation("TestCreateDebuggingInterface pid {0} {1:X16} {2}", debuggeeInfo.ProcessId, continueEventHandles[i].ToInt64(), moduleNames[i]);
+                    AssertX.FileExists("ModuleFilePath", moduleNames[i], debuggeeInfo.Output);
+
+                    AssertResult(DbgShimAPI.CreateVersionStringFromModule(debuggeeInfo.ProcessId, moduleNames[i], out string versionString));
+                    Trace.TraceInformation("TestCreateDebuggingInterface pid {0} version string {1}", debuggeeInfo.ProcessId, versionString);
+                    Assert.False(string.IsNullOrWhiteSpace(versionString));
+
+                    ICorDebug corDebug = null;
+                    string applicationGroupId = null;
+                    HResult result;
+                    switch (api)
+                    {
+                        case 0:
+                            result = DbgShimAPI.CreateDebuggingInterfaceFromVersion(versionString, out corDebug);
+                            break;
+                        case 1:
+                            result = DbgShimAPI.CreateDebuggingInterfaceFromVersionEx(DbgShimAPI.CorDebugVersion_4_0, versionString, out corDebug);
+                            break;
+                        case 2:
+                            result = DbgShimAPI.CreateDebuggingInterfaceFromVersion2(DbgShimAPI.CorDebugVersion_4_0, versionString, applicationGroupId, out corDebug);
+                            break;
+                        case 3:
+                            LibraryProviderWrapper libraryProvider = new(config.RuntimeModulePath(), config.DbiModulePath(), config.DacModulePath());
+                            result = DbgShimAPI.CreateDebuggingInterfaceFromVersion3(DbgShimAPI.CorDebugVersion_4_0, versionString, applicationGroupId, libraryProvider.ILibraryProvider, out corDebug);
+                            break;
+                        default:
+                            throw new ArgumentException(nameof(api));
+                    }
+
+                    Trace.TraceInformation("TestCreateDebuggingInterface pid {0} after API {1} call", debuggeeInfo.ProcessId, api);
+
+                    switch (api)
+                    {
+                        case 0:
+                        case 1:
+                        case 2:
+                            // The old APIs fail on single file apps
+                            Assert.Equal(!debuggeeInfo.TestConfiguration.PublishSingleFile, result);
+                            break;
+                        case 3:
+                            // The new API should always succeed
+                            AssertResult(result);
+                            break;
+                    }
+
+                    if (result)
+                    {
+                        TestICorDebug(debuggeeInfo, corDebug);
+                        AssertResult(debuggeeInfo.WaitForCreateProcess());
+                        Assert.Equal(0, corDebug.Release());
+                    }
+                    else
+                    {
+                        Trace.TraceInformation("TestCreateDebuggingInterface pid {0} FAILED {1}", debuggeeInfo.ProcessId, result);
+                    }
+                }
+            });
+            AssertResult(hr);
+            Trace.TraceInformation("TestCreateDebuggingInterface pid {0} DONE", debuggeeInfo.ProcessId);
+        }
+
+        private static readonly Guid IID_ICorDebugProcess = new Guid("3D6F5F64-7538-11D3-8D5B-00104B35E7EF");
+
+        private static void TestICorDebug(DebuggeeInfo debuggeeInfo, ICorDebug corDebug)
+        {
+            Assert.NotNull(corDebug);
+            AssertResult(corDebug.Initialize());
+            ManagedCallbackWrapper managedCallback = new(debuggeeInfo);
+            AssertResult(corDebug.SetManagedHandler(managedCallback.ICorDebugManagedCallback));
+            Trace.TraceInformation("TestICorDebug pid before DebugActiveProcess {0}", debuggeeInfo.ProcessId);
+            AssertResult(corDebug.DebugActiveProcess(debuggeeInfo.ProcessId, out IntPtr process));
+            Trace.TraceInformation("TestICorDebug pid after DebugActiveProcess {0}", debuggeeInfo.ProcessId);
+            AssertResult(COMHelper.QueryInterface(process, IID_ICorDebugProcess, out IntPtr icdp));
+            Assert.True(icdp != IntPtr.Zero);
+            COMHelper.Release(icdp);
+        }
+
+        /// <summary>
+        /// The reason we are running each test in it's own process using the remote executor is that the DBI/DAC are
+        /// never unloaded (and the existing dbgshim interfaces don't allow a way to do this). This is a problem on
+        /// Linux/MacOS to have multiple DAC loaded because DBI references the DAC's PAL exports and the loader gets
+        /// confused which DAC should be used for which DBI. This isn't a problem on Windows since there is no PAL.
+        /// </summary>
+        /// <param name="config">test configuration</param>
+        /// <param name="testName">name of test for the log file</param>
+        /// <param name="method">delegate to call in the remote process</param>
+        private async Task RemoteInvoke(TestConfiguration config, string testName, Func<string, Task<int>> method)
+        {
+            string singlefile = config.PublishSingleFile ? ".SingleFile" : "";
+            testName = $"DbgShim.UnitTests{singlefile}.{testName}";
+            string dumpPath = Path.Combine(config.LogDirPath, testName + ".dmp");
+            using TestRunner.OutputHelper output = TestRunner.ConfigureLogging(config, Output, testName);
+            int exitCode = await RemoteExecutorHelper.RemoteInvoke(output, config, TimeSpan.FromMinutes(5), dumpPath, method);
+            Assert.Equal(0, exitCode);
+        }
+
+        /// <summary>
+        /// Used in the remote invoke delegate to deserialize the test configuration xml and setup test output logging.
+        /// </summary>
+        /// <param name="config">test configuration xml</param>
+        /// <param name="config">test configuration instance</param>
+        /// <param name="output">test output instance</param>
+        private static void AfterInvoke(string configXml, out TestConfiguration config, out ITestOutputHelper output)
+        {
+            config = TestConfiguration.Deserialize(configXml);
+            output = new ConsoleTestOutputHelper();
+            LoggingListener.EnableListener(output, ListenerName);
+        }
+
+        private static void AssertResult(HResult hr)
+        {
+            Assert.Equal<HResult>(HResult.S_OK, hr);
+        }
+
+        #endregion
+    }
+
+    public static class DbgShimTestExtensions
+    {
+        public static string DbgShimPath(this TestConfiguration config)
+        {
+            return TestConfiguration.MakeCanonicalPath(config.GetValue("DbgShimPath"));
+        }
+
+        public static string RuntimeModulePath(this TestConfiguration config)
+        {
+            return TestConfiguration.MakeCanonicalPath(config.GetValue("RuntimeModulePath"));
+        }
+
+        public static string DbiModulePath(this TestConfiguration config)
+        {
+            return TestConfiguration.MakeCanonicalPath(config.GetValue("DbiModulePath"));
+        }
+
+        public static string DacModulePath(this TestConfiguration config)
+        {
+            return TestConfiguration.MakeCanonicalPath(config.GetValue("DacModulePath"));
+        }
+
+        public static string DebuggeeName(this TestConfiguration config)
+        {
+            return config.GetValue("DebuggeeName");
+        }
+    }
+}
diff --git a/src/tests/DbgShim.UnitTests/DebuggeeInfo.cs b/src/tests/DbgShim.UnitTests/DebuggeeInfo.cs
new file mode 100644 (file)
index 0000000..3ac8d3d
--- /dev/null
@@ -0,0 +1,138 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Diagnostics.Runtime.Utilities;
+using Microsoft.Diagnostics.TestHelpers;
+using System;
+using System.Diagnostics;
+using System.IO.Pipes;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.Diagnostics
+{
+    public class DebuggeeInfo : IDisposable
+    {
+        public readonly ITestOutputHelper Output;
+        public readonly TestConfiguration TestConfiguration;
+        public readonly bool Launch;
+        public readonly string PipeName;
+
+        public int ProcessId { get; private set; }
+        public IntPtr ResumeHandle { get; set; }
+
+        private readonly AutoResetEvent _createProcessEvent = new AutoResetEvent(false);
+        private readonly NamedPipeServerStream _pipeServer;
+        private HResult _createProcessResult = HResult.E_FAIL;
+        private Process _process;
+
+        public DebuggeeInfo(ITestOutputHelper output, TestConfiguration config, bool launch)
+        {
+            Output = output;
+            TestConfiguration = config;
+            Launch = launch;
+            PipeName = Guid.NewGuid().ToString();
+            _pipeServer = new NamedPipeServerStream(PipeName);
+        }
+
+        public void SetProcessId(int processId)
+        {
+            ProcessId = processId;
+            try
+            {
+                _process = Process.GetProcessById(processId);
+            }
+            catch (Exception ex) when (ex is ArgumentException || ex is InvalidOperationException)
+            {
+                Trace.TraceError($"DebuggeeInfo.SetProcessId({processId}): {ex}");
+            }
+        }
+
+        public void SetCreateProcessResult(HResult hr)
+        {
+            _createProcessResult = hr;
+            _createProcessEvent.Set();
+        }
+
+        public HResult WaitForCreateProcess()
+        {
+            Assert.True(_createProcessEvent.WaitOne());
+            return _createProcessResult;
+        }
+
+        public async Task<bool> WaitForDebuggee()
+        {
+            if (_process is null)
+            {
+                Trace.TraceWarning("DebuggeeInfo.WaitForDebuggee: no process");
+                return true;
+            }
+            try
+            {
+                var source = new CancellationTokenSource(TimeSpan.FromMinutes(5));
+                Trace.TraceInformation($"DebuggeeInfo.WaitForDebuggee: waiting {ProcessId}");
+                await _pipeServer.WaitForConnectionAsync(source.Token);
+                Trace.TraceInformation($"DebuggeeInfo.WaitForDebuggee: after wait {ProcessId}");
+            }
+            catch (OperationCanceledException ex)
+            {
+                Trace.TraceError($"DebuggeeInfo.WaitForDebuggee: canceled {ex}");
+                return false;
+            }
+            return true;
+        }
+
+        public HResult ResumeDebuggee()
+        {
+            HResult result = HResult.S_OK;
+            if (ResumeHandle != IntPtr.Zero)
+            {
+                Trace.TraceInformation($"DebuggeeInfo.ResumeDebuggee {ProcessId} handle {ResumeHandle:X8}");
+                result = DbgShimAPI.ResumeProcess(ResumeHandle);
+                DbgShimAPI.CloseResumeHandle(ResumeHandle);
+                ResumeHandle = IntPtr.Zero;
+            }
+            return result;
+        }
+
+        public void Disconnect()
+        {
+            try
+            {
+                _pipeServer.Disconnect();
+            }
+            catch (Exception ex) when (ex is InvalidOperationException)
+            {
+                Trace.TraceError(ex.ToString());
+            }
+        }
+
+        public void Kill()
+        {
+            if (_process is not null)
+            {
+                Trace.TraceInformation($"DebuggeeInfo: kill process {ProcessId}");
+                try
+                {
+                    _process.Kill();
+                    _process = null;
+                }
+                catch (Exception ex) when (ex is NotSupportedException || ex is InvalidOperationException)
+                {
+                    Trace.TraceError(ex.ToString());
+                }
+            }
+        }
+
+        public void Dispose()
+        {
+            Trace.TraceInformation($"DebuggeeInfo: disposing process {ProcessId}");
+            ResumeDebuggee();
+            _pipeServer.Dispose();
+            Kill();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/tests/DbgShim.UnitTests/Debuggees/SimpleDebuggee/SimpleDebuggee.cs b/src/tests/DbgShim.UnitTests/Debuggees/SimpleDebuggee/SimpleDebuggee.cs
new file mode 100644 (file)
index 0000000..0e870ae
--- /dev/null
@@ -0,0 +1,43 @@
+using System;
+using System.Diagnostics;
+using System.IO.Pipes;
+using System.Threading;
+
+class Simple
+{
+    public static int Main(string[] args)
+    {
+        string pipeServerName = args[0];
+        int pid = Process.GetCurrentProcess().Id;
+        Console.WriteLine("{0} SimpleDebuggee: pipe server: {1}", pid, pipeServerName);
+        Console.Out.Flush();
+
+        if (pipeServerName != null)
+        {
+            try
+            {
+                int timeout = TimeSpan.FromMinutes(5).Milliseconds;
+                using var pipeStream = new NamedPipeClientStream(pipeServerName);
+
+                Console.WriteLine("{0} SimpleDebuggee: connecting to pipe", pid);
+                Console.Out.Flush();
+                pipeStream.Connect(timeout);
+
+                Console.WriteLine("{0} SimpleDebuggee: connected to pipe", pid);
+                Console.Out.Flush();
+
+                // Wait for server to send something
+                int input = pipeStream.ReadByte();
+
+                Console.WriteLine("{0} SimpleDebuggee: waking up {1}", pid, input);
+                Console.Out.Flush();
+            }
+            catch (Exception ex)
+            {
+                Console.Error.WriteLine(ex.ToString());
+                Console.Error.Flush();
+            }
+        }
+        return 0;
+    }
+}
diff --git a/src/tests/DbgShim.UnitTests/Debuggees/SimpleDebuggee/SimpleDebuggee.csproj b/src/tests/DbgShim.UnitTests/Debuggees/SimpleDebuggee/SimpleDebuggee.csproj
new file mode 100644 (file)
index 0000000..03cd2f5
--- /dev/null
@@ -0,0 +1,7 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework Condition="'$(BuildProjectFramework)' != ''">$(BuildProjectFramework)</TargetFramework>
+    <TargetFrameworks Condition="'$(BuildProjectFramework)' == ''">$(BuildTargetFrameworks)</TargetFrameworks>
+  </PropertyGroup>
+</Project>
diff --git a/src/tests/DbgShim.UnitTests/Debuggees/SimpleDebuggee/runtimeconfig.template.json b/src/tests/DbgShim.UnitTests/Debuggees/SimpleDebuggee/runtimeconfig.template.json
new file mode 100644 (file)
index 0000000..f022b7f
--- /dev/null
@@ -0,0 +1,3 @@
+{
+  "rollForwardOnNoCandidateFx": 2
+}
\ No newline at end of file
diff --git a/src/tests/DbgShim.UnitTests/ICLRDebugging.cs b/src/tests/DbgShim.UnitTests/ICLRDebugging.cs
new file mode 100644 (file)
index 0000000..cd2ca5e
--- /dev/null
@@ -0,0 +1,57 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Diagnostics.Runtime;
+using Microsoft.Diagnostics.Runtime.Utilities;
+using SOS.Hosting;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Diagnostics
+{    
+    public unsafe class ICLRDebugging : CallableCOMWrapper
+    {
+        public static readonly Guid IID_ICLRDebugging = new Guid("D28F3C5A-9634-4206-A509-477552EEFB10");
+        public static readonly Guid CLSID_ICLRDebugging = new Guid("BACC578D-FBDD-48A4-969F-02D932B74634");
+
+        private ref readonly ICLRDebuggingVTable VTable => ref Unsafe.AsRef<ICLRDebuggingVTable>(_vtable);
+
+        public static ICLRDebugging Create(IntPtr punk) => punk != IntPtr.Zero ? new ICLRDebugging(punk) : null;
+
+        private ICLRDebugging(IntPtr punk)
+            : base(new RefCountedFreeLibrary(IntPtr.Zero), IID_ICLRDebugging, punk)
+        {
+            SuppressRelease();
+        }
+
+        public HResult OpenVirtualProcess(
+            ulong moduleBaseAddress,
+            IntPtr dataTarget,
+            IntPtr libraryProvider,
+            ClrDebuggingVersion maxDebuggerSupportedVersion,
+            in Guid riidProcess,
+            out IntPtr process,
+            out ClrDebuggingVersion version, 
+            out ClrDebuggingProcessFlags flags)
+        {
+            return VTable.OpenVirtualProcess(
+                Self,
+                moduleBaseAddress,
+                dataTarget,
+                libraryProvider,
+                in maxDebuggerSupportedVersion,
+                in riidProcess,
+                out process,
+                out version,
+                out flags);
+        }
+
+        [StructLayout(LayoutKind.Sequential)]
+        private readonly unsafe struct ICLRDebuggingVTable
+        {
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, ulong, IntPtr, IntPtr, in ClrDebuggingVersion, in Guid, out IntPtr, out ClrDebuggingVersion, out ClrDebuggingProcessFlags, HResult> OpenVirtualProcess;
+        }
+    }
+}
diff --git a/src/tests/DbgShim.UnitTests/ICorDebug.cs b/src/tests/DbgShim.UnitTests/ICorDebug.cs
new file mode 100644 (file)
index 0000000..18a0fcf
--- /dev/null
@@ -0,0 +1,46 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Diagnostics.Runtime;
+using Microsoft.Diagnostics.Runtime.Utilities;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Diagnostics
+{    
+    public unsafe class ICorDebug : CallableCOMWrapper
+    {
+        private static readonly Guid IID_ICorDebug = new Guid("3d6f5f61-7538-11d3-8d5b-00104b35e7ef");
+
+        private ref readonly ICorDebugVTable VTable => ref Unsafe.AsRef<ICorDebugVTable>(_vtable);
+
+        public static ICorDebug Create(IntPtr punk) => punk != IntPtr.Zero ? new ICorDebug(punk) : null;
+
+        private ICorDebug(IntPtr punk)
+            : base(new RefCountedFreeLibrary(IntPtr.Zero), IID_ICorDebug, punk)
+        {
+            SuppressRelease();
+        }
+
+        public HResult Initialize() => VTable.Initialize(Self);
+
+        public HResult Terminate() => VTable.Terminate(Self);
+
+        public HResult SetManagedHandler(IntPtr managedCallback) => VTable.SetManangedHandler(Self, managedCallback);
+
+        public HResult DebugActiveProcess(int processId, out IntPtr process) => VTable.DebugActiveProcess(Self, processId, 0, out process);
+
+        [StructLayout(LayoutKind.Sequential)]
+        private readonly unsafe struct ICorDebugVTable
+        {
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, HResult> Initialize;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, HResult> Terminate;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, IntPtr, HResult> SetManangedHandler;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, IntPtr, HResult> SetUnmanangedHandler;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, HResult> CreateProcess;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, int, int, out IntPtr, HResult> DebugActiveProcess;
+        }
+    }
+}
diff --git a/src/tests/DbgShim.UnitTests/ICorDebugController.cs b/src/tests/DbgShim.UnitTests/ICorDebugController.cs
new file mode 100644 (file)
index 0000000..042b013
--- /dev/null
@@ -0,0 +1,48 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Diagnostics.Runtime;
+using Microsoft.Diagnostics.Runtime.Utilities;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Diagnostics
+{    
+    public unsafe class ICorDebugController : CallableCOMWrapper
+    {
+        private static readonly Guid IID_ICorDebugController = new Guid("3D6F5F62-7538-11D3-8D5B-00104B35E7EF");
+
+        private ref readonly ICorDebugControllerVTable VTable => ref Unsafe.AsRef<ICorDebugControllerVTable>(_vtable);
+
+        public static ICorDebugController Create(IntPtr punk) => punk != IntPtr.Zero ? new ICorDebugController(punk) : null;
+
+        private ICorDebugController(IntPtr punk)
+            : base(new RefCountedFreeLibrary(IntPtr.Zero), IID_ICorDebugController, punk)
+        {
+            SuppressRelease();
+        }
+
+        public HResult Stop() => VTable.Stop(Self, uint.MaxValue);
+
+        public HResult Continue(bool isOutOfBand) => VTable.Continue(Self, isOutOfBand ? 1 : 0);
+
+        public HResult Detach() => VTable.Detach(Self);
+
+        public HResult Terminate(uint exitCode) => VTable.Terminate(Self, exitCode);
+
+        [StructLayout(LayoutKind.Sequential)]
+        private readonly unsafe struct ICorDebugControllerVTable
+        {
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, uint, HResult> Stop;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, int, HResult> Continue;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, HResult> IsRunning_dummy;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, HResult> HasQueuedCallbacks_dummy;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, HResult> EnumerateThreads_dummy;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, HResult> SetAllThreadsDebugState_dummy;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, HResult> Detach;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, uint, HResult> Terminate;
+        }
+    }
+}
diff --git a/src/tests/DbgShim.UnitTests/LibraryProviderWrapper.cs b/src/tests/DbgShim.UnitTests/LibraryProviderWrapper.cs
new file mode 100644 (file)
index 0000000..63e8541
--- /dev/null
@@ -0,0 +1,509 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Diagnostics.DebugServices;
+using Microsoft.Diagnostics.DebugServices.Implementation;
+using Microsoft.Diagnostics.Runtime;
+using Microsoft.Diagnostics.Runtime.Utilities;
+using Microsoft.FileFormats;
+using Microsoft.FileFormats.PE;
+using Microsoft.SymbolStore;
+using Microsoft.SymbolStore.KeyGenerators;
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using Xunit;
+
+namespace SOS.Hosting
+{
+    public sealed unsafe class LibraryProviderWrapper : COMCallableIUnknown, IHost
+    {
+        public enum LIBRARY_PROVIDER_INDEX_TYPE
+        {
+            Unknown = 0,
+            Identity = 1,
+            Runtime = 2,
+        };
+
+        public static readonly Guid IID_ICLRDebuggingLibraryProvider = new Guid("3151C08D-4D09-4f9b-8838-2880BF18FE51");
+        public static readonly Guid IID_ICLRDebuggingLibraryProvider2 = new Guid("E04E2FF1-DCFD-45D5-BCD1-16FFF2FAF7BA");
+        public static readonly Guid IID_ICLRDebuggingLibraryProvider3 = new Guid("DE3AAB18-46A0-48B4-BF0D-2C336E69EA1B");
+
+        public IntPtr ILibraryProvider { get; }
+
+        private readonly OSPlatform _targetOS;
+        private readonly ImmutableArray<byte> _runtimeModuleBuildId;
+        private readonly string _dbiModulePath;
+        private readonly string _dacModulePath;
+        private ISymbolService _symbolService;
+
+        public LibraryProviderWrapper(string runtimeModulePath, string dbiModulePath, string dacModulePath)
+           : this(GetRunningOS(), GetBuildId(runtimeModulePath), dbiModulePath, dacModulePath)
+        {
+        }
+
+        public LibraryProviderWrapper(OSPlatform targetOS, ImmutableArray<byte> runtimeModuleBuildId, string dbiModulePath, string dacModulePath)
+        {
+            _targetOS = targetOS;
+            _runtimeModuleBuildId = runtimeModuleBuildId;
+            _dbiModulePath = dbiModulePath;
+            _dacModulePath = dacModulePath;
+
+            VTableBuilder builder = AddInterface(IID_ICLRDebuggingLibraryProvider, validate: false);
+            builder.AddMethod(new ProvideLibraryDelegate(ProvideLibrary));
+            ILibraryProvider = builder.Complete();
+
+            builder = AddInterface(IID_ICLRDebuggingLibraryProvider2, validate: false);
+            builder.AddMethod(new ProvideLibrary2Delegate(ProvideLibrary2));
+            builder.Complete();
+
+            builder = AddInterface(IID_ICLRDebuggingLibraryProvider3, validate: false);
+            builder.AddMethod(new ProvideWindowsLibraryDelegate(ProvideWindowsLibrary));
+            builder.AddMethod(new ProvideUnixLibraryDelegate(ProvideUnixLibrary));
+            builder.Complete();
+
+            AddRef();
+        }
+
+        private static OSPlatform GetRunningOS()
+        {
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return OSPlatform.Windows;
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return OSPlatform.Linux;
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return OSPlatform.OSX;
+            throw new NotSupportedException($"OS not supported {RuntimeInformation.OSDescription}");
+        }
+
+        protected override void Destroy()
+        {
+            Trace.TraceInformation("LibraryProviderWrapper.Destroy");
+        }
+
+        private HResult ProvideLibrary(
+            IntPtr self,
+            string fileName,
+            uint timeStamp,
+            uint sizeOfImage,
+            out IntPtr moduleHandle)
+        {
+            Trace.TraceInformation($"LibraryProviderWrapper.ProvideLibrary {fileName} {timeStamp:X8} {sizeOfImage:X8}");
+            try
+            {
+                // This should only be called when hosted on Windows because of the PAL module handle problems
+                Assert.True(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
+
+                string modulePath = null;
+                if (fileName.Contains(DbiName))
+                {
+                    if (_dbiModulePath is not null)
+                    {
+                        modulePath = _dbiModulePath;
+                    }
+                    else
+                    {
+                        modulePath = DownloadModule(DbiName, timeStamp, sizeOfImage);
+                    }
+                }
+                // This needs to work for long named DAC's so remove the extension
+                else if (fileName.Contains(Path.GetFileNameWithoutExtension(DacName)))
+                {
+                    if (_dacModulePath is not null)
+                    {
+                        modulePath = _dacModulePath;
+                    }
+                    else
+                    {
+                        modulePath = DownloadModule(DacName, timeStamp, sizeOfImage);
+                    }
+                }
+                TestGetPEInfo(modulePath, timeStamp, sizeOfImage);
+                moduleHandle = DataTarget.PlatformFunctions.LoadLibrary(modulePath);
+                Trace.TraceInformation($"LibraryProviderWrapper.ProvideLibrary SUCCEEDED {modulePath}");
+                return HResult.S_OK;
+            }
+            catch (Exception ex)
+            {
+                Trace.TraceError($"LibraryProviderWrapper.ProvideLibrary {ex}");
+            }
+            Trace.TraceError($"LibraryProviderWrapper.ProvideLibrary FAILED");
+            moduleHandle = IntPtr.Zero;
+            return HResult.E_INVALIDARG;
+        }
+
+        private HResult ProvideLibrary2(
+            IntPtr self,
+            string fileName,
+            uint timeStamp,
+            uint sizeOfImage,
+            out IntPtr modulePathOut)
+        {
+            Trace.TraceInformation($"LibraryProviderWrapper.ProvideLibrary2 {fileName} {timeStamp:X8} {sizeOfImage:X8}");
+            try
+            {
+                string modulePath = null;
+                if (fileName.Contains(DbiName))
+                {
+                    if (_dbiModulePath != null)
+                    {
+                        modulePath = _dbiModulePath; 
+                    }
+                    else 
+                    {
+                        modulePath = DownloadModule(DbiName, timeStamp, sizeOfImage);
+                    }
+                }
+                // This needs to work for long named DAC's so remove the extension
+                else if (fileName.Contains(Path.GetFileNameWithoutExtension(DacName)))
+                {
+                    if (_dacModulePath != null)
+                    {
+                        modulePath = _dacModulePath;
+                    }
+                    else
+                    {
+                        modulePath = DownloadModule(DacName, timeStamp, sizeOfImage);
+                    }
+                }
+                // If this is called on Linux or MacOS don't verify. This should only happen if
+                // these tests are run against an old dbgshim version.
+                if (_targetOS == OSPlatform.Windows)
+                {
+                    TestGetPEInfo(modulePath, timeStamp, sizeOfImage);
+                }
+                modulePathOut = Marshal.StringToCoTaskMemUni(modulePath); 
+                Trace.TraceInformation($"LibraryProviderWrapper.ProvideLibrary2 SUCCEEDED {modulePath}");
+                return HResult.S_OK;
+            }
+            catch (Exception ex)
+            {
+                Trace.TraceError($"LibraryProviderWrapper.ProvideLibrary2 {ex}");
+            }
+            Trace.TraceError("LibraryProviderWrapper.ProvideLibrary2 FAILED");
+            modulePathOut = IntPtr.Zero;
+            return HResult.E_INVALIDARG;
+        }
+
+        private HResult ProvideWindowsLibrary(
+            IntPtr self,
+            string fileName,
+            string runtimeModulePath,
+            LIBRARY_PROVIDER_INDEX_TYPE indexType,
+            uint timeStamp,
+            uint sizeOfImage,
+            out IntPtr modulePathOut)
+        {
+            Trace.TraceInformation($"LibraryProviderWrapper.ProvideWindowsLibrary {fileName} {runtimeModulePath} {timeStamp:X8} {sizeOfImage:X8}");
+            try
+            {
+                // Should only be called for Windows targets
+                Assert.Equal(OSPlatform.Windows, _targetOS);
+
+                // Should always get the identity on Windows
+                Assert.Equal(LIBRARY_PROVIDER_INDEX_TYPE.Identity, indexType);
+
+                string modulePath = null;
+                if (fileName.Contains(DbiName))
+                {
+                    if (_dbiModulePath != null)
+                    {
+                        modulePath = _dbiModulePath; 
+                    }
+                    else 
+                    {
+                        modulePath = DownloadModule(DbiName, timeStamp, sizeOfImage);
+                    }
+                }
+                // This needs to work for long named DAC's so remove the extension
+                else if (fileName.Contains(Path.GetFileNameWithoutExtension(DacName)))
+                {
+                    if (_dacModulePath != null)
+                    {
+                        modulePath = _dacModulePath;
+                    }
+                    else
+                    {
+                        modulePath = DownloadModule(DacName, timeStamp, sizeOfImage);
+                    }
+                }
+                TestGetPEInfo(modulePath, timeStamp, sizeOfImage);
+                modulePathOut = Marshal.StringToCoTaskMemUni(modulePath); 
+                Trace.TraceInformation($"LibraryProviderWrapper.ProvideWindowsLibrary SUCCEEDED {modulePath}");
+                return HResult.S_OK;
+            }
+            catch (Exception ex)
+            {
+                Trace.TraceError($"LibraryProviderWrapper.ProvideWindowsLibrary {ex}");
+            }
+            Trace.TraceError("LibraryProviderWrapper.ProvideWindowsLibrary FAILED");
+            modulePathOut = IntPtr.Zero;
+            return HResult.E_INVALIDARG;
+        }
+
+        private HResult ProvideUnixLibrary(
+            IntPtr self,
+            string fileName,
+            string runtimeModulePath,
+            LIBRARY_PROVIDER_INDEX_TYPE indexType,
+            byte* buildIdBytes,
+            int buildIdSize,
+            out IntPtr modulePathOut)
+        {
+            try
+            {
+                // Should only be called for Unix targets
+                Assert.NotEqual(OSPlatform.Windows, _targetOS);
+
+                byte[] buildId = Array.Empty<byte>();
+                string modulePath = null;
+                if (buildIdBytes != null && buildIdSize > 0)
+                {
+                    Span<byte> span = new Span<byte>(buildIdBytes, buildIdSize);
+                    buildId = span.ToArray();
+                }
+                Trace.TraceInformation($"LibraryProviderWrapper.ProvideUnixLibrary {fileName} {runtimeModulePath} {indexType} {string.Concat(buildId.Select((b) => b.ToString("x2")))}");
+                if (fileName.Contains(DbiName))
+                {
+                    if (_dbiModulePath != null)
+                    {
+                        modulePath = _dbiModulePath;
+                    }
+                    else
+                    {
+                        modulePath = DownloadModule(DbiName, buildId);
+                    }
+                }
+                else if (fileName.Contains(DacName))
+                {
+                    if (_dacModulePath != null)
+                    {
+                        modulePath = _dacModulePath;
+                    }
+                    else
+                    {
+                        modulePath = DownloadModule(DacName, buildId);
+                    }
+                }
+                if (modulePath != null)
+                {
+                    switch (indexType)
+                    {
+                        case LIBRARY_PROVIDER_INDEX_TYPE.Identity:
+                            TestBuildId(GetBuildId(modulePath), buildId);
+                            break;
+
+                        case LIBRARY_PROVIDER_INDEX_TYPE.Runtime:
+                            TestBuildId(_runtimeModuleBuildId, buildId);
+                            break;
+                    }
+                    modulePathOut = Marshal.StringToCoTaskMemUni(modulePath);
+                    Trace.TraceInformation($"LibraryProviderWrapper.ProvideUnixLibrary SUCCEEDED {modulePath}");
+                    return HResult.S_OK;
+                }
+            }
+            catch (Exception ex)
+            {
+                Trace.TraceError($"LibraryProviderWrapper.ProvideUnixLibrary {ex}");
+            }
+            Trace.TraceError("LibraryProviderWrapper.ProvideUnixLibrary FAILED");
+            modulePathOut = IntPtr.Zero;
+            return HResult.E_INVALIDARG;
+        }
+
+        private string DownloadModule(string moduleName, uint timeStamp, uint sizeOfImage)
+        {
+            Assert.True(timeStamp != 0 && sizeOfImage != 0);
+            SymbolStoreKey key = PEFileKeyGenerator.GetKey(moduleName, timeStamp, sizeOfImage);
+            Assert.NotNull(key);
+            string downloadedPath = SymbolService.DownloadFile(key);
+            Assert.NotNull(downloadedPath);
+            return downloadedPath;
+        }
+
+        private string DownloadModule(string moduleName, byte[] buildId)
+        {
+            Assert.True(buildId.Length > 0);
+            SymbolStoreKey key = null;
+            OSPlatform platform;
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+            {
+                // This is the cross-DAC case when OpenVirtualProcess calls on a Linux/MacOS dump. Should never
+                // get here for a Windows dump or for live sessions (RegisterForRuntimeStartup, etc).
+                platform = _targetOS;
+            }
+            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+            {
+                platform = OSPlatform.Linux;
+            }
+            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+            {
+                platform = OSPlatform.OSX;
+            }
+            else
+            {
+                throw new NotSupportedException($"OS not supported {RuntimeInformation.OSDescription}");
+            }
+            if (platform == OSPlatform.Linux)
+            {
+                key = ELFFileKeyGenerator.GetKeys(KeyTypeFlags.IdentityKey, moduleName, buildId, symbolFile: false, symbolFileName: null).SingleOrDefault();
+            }
+            else if (platform == OSPlatform.OSX)
+            {
+                key = MachOFileKeyGenerator.GetKeys(KeyTypeFlags.IdentityKey, moduleName, buildId, symbolFile: false, symbolFileName: null).SingleOrDefault();
+            }
+            Assert.NotNull(key);
+            string downloadedPath = SymbolService.DownloadFile(key);
+            Assert.NotNull(downloadedPath);
+            return downloadedPath;
+        }
+
+        private void TestGetPEInfo(string filePath, uint timeStamp, uint sizeOfImage)
+        {
+            if (filePath != null && timeStamp != 0 && sizeOfImage != 0)
+            {
+                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+                {
+                    using Stream stream = Utilities.TryOpenFile(filePath);
+                    if (stream is not null)
+                    {
+                        var peFile = new PEFile(new StreamAddressSpace(stream), false);
+                        if (peFile.IsValid())
+                        {
+                            Assert.Equal(peFile.Timestamp, timeStamp);
+                            Assert.Equal(peFile.SizeOfImage, sizeOfImage);
+                            return;
+                        }
+                    }
+                    throw new ArgumentException($"GetPEInfo {filePath} not valid PE file");
+                }
+            }
+        }
+
+        private void TestBuildId(ImmutableArray<byte> expectedBuildId, byte[] actualBuildId)
+        {
+            if (expectedBuildId.Length > 0)
+            {
+                Assert.Equal(expectedBuildId, actualBuildId);
+            }
+        }
+
+        private static ImmutableArray<byte> GetBuildId(string filePath)
+        {
+            if (filePath != null)
+            {
+                if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+                {
+                    using Utilities.ELFModule elfModule = Utilities.OpenELFFile(filePath);
+                    if (elfModule is not null)
+                    {
+                        return elfModule.BuildID.ToImmutableArray();
+                    }
+                    throw new ArgumentException($"TestBuildId {filePath} not valid ELF file");
+                }
+                else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+                {
+                    using Utilities.MachOModule machOModule = Utilities.OpenMachOFile(filePath);
+                    if (machOModule is not null)
+                    {
+                        return machOModule.Uuid.ToImmutableArray();
+                    }
+                    throw new ArgumentException($"TestBuildId {filePath} not valid MachO file");
+                }
+            }
+            return ImmutableArray<byte>.Empty;
+        }
+
+        private string DbiName
+        {
+            get
+            {
+                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return "mscordbi.dll";
+                if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return "libmscordbi.so";
+                if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return "libmscordbi.dylib";
+                throw new NotSupportedException($"OS not supported {RuntimeInformation.OSDescription}");
+            }
+        }
+
+        private string DacName 
+        {
+            get 
+            {
+                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return "mscordaccore.dll";
+                if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return "libmscordaccore.so";
+                if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return "libmscordaccore.dylib";
+                throw new NotSupportedException($"OS not supported {RuntimeInformation.OSDescription}");
+            }
+        }
+
+        private ISymbolService SymbolService
+        {
+            get 
+            {
+                if (_symbolService is null)
+                {
+                    _symbolService = new SymbolService(this);
+                    _symbolService.AddSymbolServer(msdl: true, symweb: false, symbolServerPath: null, authToken: null, timeoutInMinutes: 0);
+                    _symbolService.AddCachePath(SymbolService.DefaultSymbolCache);
+                }
+                return _symbolService;
+            }
+        }
+
+        #region IHost
+
+        IServiceEvent IHost.OnShutdownEvent => throw new NotImplementedException();
+
+        HostType IHost.HostType => HostType.DotnetDump;
+
+        IServiceProvider IHost.Services => throw new NotImplementedException();
+
+        IEnumerable<ITarget> IHost.EnumerateTargets() => throw new NotImplementedException();
+
+        void IHost.DestroyTarget(ITarget target) => throw new NotImplementedException();
+
+        #endregion
+
+        #region ICLRDebuggingLibraryProvider* delegates
+
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult ProvideLibraryDelegate(
+            [In] IntPtr self,
+            [In, MarshalAs(UnmanagedType.LPWStr)] string fileName,
+            [In] uint timeStamp,
+            [In] uint sizeOfImage,
+            out IntPtr moduleHandle);
+
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult ProvideLibrary2Delegate(
+            [In] IntPtr self,
+            [In, MarshalAs(UnmanagedType.LPWStr)] string fileName,
+            [In] uint timeStamp,
+            [In] uint sizeOfImage,
+            out IntPtr modulePath);
+
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult ProvideWindowsLibraryDelegate(
+            [In] IntPtr self,
+            [In, MarshalAs(UnmanagedType.LPWStr)] string fileName,
+            [In, MarshalAs(UnmanagedType.LPWStr)] string runtimeModulePath,
+            [In] LIBRARY_PROVIDER_INDEX_TYPE indexType,
+            [In] uint timeStamp,
+            [In] uint sizeOfImage,
+            out IntPtr modulePath);
+
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult ProvideUnixLibraryDelegate(
+            [In] IntPtr self,
+            [In, MarshalAs(UnmanagedType.LPWStr)] string fileName,
+            [In, MarshalAs(UnmanagedType.LPWStr)] string runtimeModulePath,
+            [In] LIBRARY_PROVIDER_INDEX_TYPE indexType,
+            [In] byte* buildIdBytes,
+            [In] int buildIdSize,
+            out IntPtr modulePath);
+
+        #endregion
+    }
+}
diff --git a/src/tests/DbgShim.UnitTests/ManagedCallbackWrapper.cs b/src/tests/DbgShim.UnitTests/ManagedCallbackWrapper.cs
new file mode 100644 (file)
index 0000000..75e9094
--- /dev/null
@@ -0,0 +1,193 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Diagnostics.Runtime.Utilities;
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Diagnostics
+{
+    public sealed class ManagedCallbackWrapper : COMCallableIUnknown 
+    {
+        private static readonly Guid IID_ICorDebugManagedCallback = new Guid("3D6F5F60-7538-11D3-8D5B-00104B35E7EF");
+        private static readonly Guid IID_ICorDebugManagedCallback2 = new Guid("250E5EEA-DB5C-4C76-B6F3-8C46F12E3203");
+
+        private readonly DebuggeeInfo _startInfo;
+
+        public IntPtr ICorDebugManagedCallback { get; }
+
+        public ManagedCallbackWrapper(DebuggeeInfo startInfo)
+        {
+            _startInfo = startInfo;
+
+            VTableBuilder builder = AddInterface(IID_ICorDebugManagedCallback, validate: false);
+            builder.AddMethod(new BreakpointDelegate((self, pAppDomain, pThread, pBreakpoint) => HResult.E_NOTIMPL));
+            builder.AddMethod(new StepCompleteDelegate((self, pAppDomain, pThread, pStepper, reason) => HResult.E_NOTIMPL));
+            builder.AddMethod(new BreakDelegate((self, pAppDomain, pThread) => WriteLine("Break")));
+            builder.AddMethod(new ExceptionDelegate((self, pAppDomain, pThread, unhandled) => WriteLine("Exception")));
+            builder.AddMethod(new EvalCompleteDelegate((self, pAppDomain, pThread, pEval) => HResult.E_NOTIMPL));
+            builder.AddMethod(new EvalExceptionDelegate((self, pAppDomain, pThread, pEval) => HResult.E_NOTIMPL));
+            builder.AddMethod(new CreateProcessDelegate((self, pProcess) => CreateProcess(pProcess)));
+            builder.AddMethod(new ExitProcessDelegate((self, pProcess) => WriteLine("ExitProcess")));
+            builder.AddMethod(new CreateThreadDelegate((self, pAppDomain, pThread) => HResult.E_NOTIMPL));
+            builder.AddMethod(new ExitThreadDelegate((self, pAppDomain, pThread) => HResult.E_NOTIMPL));
+            builder.AddMethod(new LoadModuleDelegate((self, pAppDomain, pModule) => HResult.E_NOTIMPL));
+            builder.AddMethod(new UnloadModuleDelegate((self, pAppDomain, pModule) => HResult.E_NOTIMPL));
+            builder.AddMethod(new LoadClassDelegate((self, pAppDomain, c) => HResult.E_NOTIMPL));
+            builder.AddMethod(new UnloadClassDelegate((self, pAppDomain, c) => HResult.E_NOTIMPL));
+            builder.AddMethod(new DebuggerErrorDelegate((self, pProcess, errorHR, errorCode) => WriteLine($"DebuggerError {errorHR} {errorCode:X8}")));
+            builder.AddMethod(new LogMessageDelegate((self, pAppDomain, pThread, lLevel, pLogSwitchName, pMessage) => HResult.E_NOTIMPL));
+            builder.AddMethod(new LogSwitchDelegate((self, pAppDomain, pThread, lLevel, ulReason, pLogSwitchName, pParentName) => HResult.E_NOTIMPL));
+            builder.AddMethod(new CreateAppDomainDelegate((self, pProcess, pAppDomain) => HResult.E_NOTIMPL));
+            builder.AddMethod(new ExitAppDomainDelegate((self, pProcess, pAppDomain) => HResult.E_NOTIMPL));
+            builder.AddMethod(new LoadAssemblyDelegate((self, pAppDomain, pAssembly) => HResult.E_NOTIMPL));
+            builder.AddMethod(new UnloadAssemblyDelegate((self, pAppDomain, pAssembly) => HResult.E_NOTIMPL));
+            builder.AddMethod(new ControlCTrapDelegate((self, pProcess) => HResult.E_NOTIMPL));
+            builder.AddMethod(new NameChangeDelegate((self, pAppDomain, pThread) => HResult.E_NOTIMPL));
+            builder.AddMethod(new UpdateModuleSymbolsDelegate((self, pAppDomain, pModule, pSymbolStream) => HResult.E_NOTIMPL));
+            builder.AddMethod(new EditAndContinueRemapDelegate((self, pAppDomain, pThread, pFunction, fAccurate) => HResult.E_NOTIMPL));
+            builder.AddMethod(new BreakpointSetErrorDelegate((self, pAppDomain, pThread, pBreakpoint, dwError) => HResult.E_NOTIMPL));
+            ICorDebugManagedCallback = builder.Complete();
+
+            builder = AddInterface(IID_ICorDebugManagedCallback2, validate: false);
+            builder.AddMethod(new FunctionRemapOpportunityDelegate((self, pAppDomain, pThread, pOldFunction, pNewFunction, oldILOffset) => HResult.E_NOTIMPL));
+            builder.AddMethod(new CreateConnectionDelegate((IntPtr self, IntPtr pProcess, uint dwConnectionId, ref ushort pConnName) => HResult.E_NOTIMPL));
+            builder.AddMethod(new ChangeConnectionDelegate((self, pProcess, dwConnectionId) => HResult.E_NOTIMPL));
+            builder.AddMethod(new DestroyConnectionDelegate((self, pProcess, dwConnectionId) => HResult.E_NOTIMPL));
+            builder.AddMethod(new ExceptionDelegate2((self, pAppDomain, pThread, pFrame, nOffset, dwEventType, dwFlags) => HResult.E_NOTIMPL));
+            builder.AddMethod(new ExceptionUnwindDelegate((self, pAppDomain, pThread, dwEventType, dwFlags) => HResult.E_NOTIMPL));
+            builder.AddMethod(new FunctionRemapCompleteDelegate((self, pAppDomain, pThread, pFunction) => HResult.E_NOTIMPL));
+            builder.AddMethod(new MDANotificationDelegate((self, pController, pThread, pMDA) => HResult.E_NOTIMPL));
+            builder.Complete();
+
+            AddRef();
+        }
+
+        private HResult CreateProcess(IntPtr pController)
+        {
+            Trace.TraceInformation("ManagedCallbackWrapper.CreateProcess");
+            ICorDebugController process = ICorDebugController.Create(pController);
+            _startInfo.SetCreateProcessResult(process.Continue(isOutOfBand: false));
+            process.Release();
+            return HResult.S_OK;
+        }
+
+        private HResult WriteLine(string message)
+        {
+            Trace.TraceInformation("ManagedCallbackWrapper." + message);
+            return HResult.E_NOTIMPL;
+        }
+
+        #region ICorDebugManagedCallback delegates
+
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult BreakpointDelegate([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr pThread, [In] IntPtr pBreakpoint);
+
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult StepCompleteDelegate([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr pThread, [In] IntPtr pStepper, [In] int reason);
+
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult BreakDelegate([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr pThread);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult ExceptionDelegate([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr pThread, [In] int unhandled);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult EvalCompleteDelegate([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr pThread, [In] IntPtr pEval);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult EvalExceptionDelegate([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr pThread, [In] IntPtr pEval);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult CreateProcessDelegate([In] IntPtr self, [In] IntPtr pProcess);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult ExitProcessDelegate([In] IntPtr self, [In] IntPtr pProcess);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult CreateThreadDelegate([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr thread);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult ExitThreadDelegate([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr thread);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult LoadModuleDelegate([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr pModule);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult UnloadModuleDelegate([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr pModule);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult LoadClassDelegate([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr c);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult UnloadClassDelegate([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr c);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult DebuggerErrorDelegate([In] IntPtr self, [In] IntPtr pProcess, [In] HResult errorHR, [In] uint errorCode);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult LogMessageDelegate([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr pThread, [In] int lLevel, [In, MarshalAs(UnmanagedType.LPWStr)] string pLogSwitchName, [In, MarshalAs(UnmanagedType.LPWStr)] string pMessage);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult LogSwitchDelegate([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr pThread, [In] int lLevel, [In] uint ulReason, [In, MarshalAs(UnmanagedType.LPWStr)] string pLogSwitchName, [In, MarshalAs(UnmanagedType.LPWStr)] string pParentName);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult CreateAppDomainDelegate([In] IntPtr self, [In] IntPtr pProcess, [In] IntPtr pAppDomain);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult ExitAppDomainDelegate([In] IntPtr self, [In] IntPtr pProcess, [In] IntPtr pAppDomain);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult LoadAssemblyDelegate([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr pAssembly);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult UnloadAssemblyDelegate([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr pAssembly);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult ControlCTrapDelegate([In] IntPtr self, [In] IntPtr pProcess);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult NameChangeDelegate([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr pThread);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult UpdateModuleSymbolsDelegate([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr pModule, [In] IntPtr pSymbolStream);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult EditAndContinueRemapDelegate([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr pThread, [In] IntPtr pFunction, [In] int fAccurate);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult BreakpointSetErrorDelegate([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr pThread, [In] IntPtr pBreakpoint, [In] uint dwError);
+
+        #endregion
+
+        #region ICorDebugManagedCallback2 delegates
+
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult FunctionRemapOpportunityDelegate([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr pThread, [In] IntPtr pOldFunction, [In] IntPtr pNewFunction, [In] uint oldILOffset);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult CreateConnectionDelegate([In] IntPtr self, [In] IntPtr pProcess, [In] uint dwConnectionId, [In] ref ushort pConnName);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult ChangeConnectionDelegate([In] IntPtr self, [In] IntPtr pProcess, [In] uint dwConnectionId);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult DestroyConnectionDelegate([In] IntPtr self, [In] IntPtr pProcess, [In] uint dwConnectionId);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult ExceptionDelegate2([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr pThread, [In] IntPtr pFrame, [In] uint nOffset, [In] int dwEventType, [In] uint dwFlags);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult ExceptionUnwindDelegate([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr pThread, [In] int dwEventType, [In] uint dwFlags);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult FunctionRemapCompleteDelegate([In] IntPtr self, [In] IntPtr pAppDomain, [In] IntPtr pThread, [In] IntPtr pFunction);
+        
+        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+        private delegate HResult MDANotificationDelegate([In] IntPtr self, [In] IntPtr pController, [In] IntPtr pThread, [In] IntPtr pMDA);
+
+        #endregion
+    }
+}
index 5514455b3a4c4856395c6be636c5bea53a1b447d..6302b9a60211b72c67c50dd4d49e90b20c5db4f5 100644 (file)
       <DumpFile>$(Package_TestAssets_Linux_x64_5_0)/LineNums/SOS.LineNums.Heap.dmp</DumpFile>
       <TestDataFile>$(Package_TestAssets_Linux_x64_5_0)/LineNums/SOS.LineNums.Heap.dmp.xml</TestDataFile>
     </Option>
+    <Option>
+      <DumpFile>$(Package_TestAssets_Linux_x64_6_0)/DivZero/SOS.DivZero.Heap.dmp</DumpFile>
+      <TestDataFile>$(Package_TestAssets_Linux_x64_6_0)/DivZero/SOS.DivZero.Heap.dmp.xml</TestDataFile>
+    </Option>
+    <Option>
+      <DumpFile>$(Package_TestAssets_Linux_x64_6_0)/DivZero/SOS.DivZero.Triage.dmp</DumpFile>
+      <TestDataFile>$(Package_TestAssets_Linux_x64_6_0)/DivZero/SOS.DivZero.Triage.dmp.xml</TestDataFile>
+    </Option>
+    <Option>
+      <DumpFile>$(Package_TestAssets_Linux_x64_6_0)/WebApp3/SOS.WebApp3.Heap.dmp</DumpFile>
+      <TestDataFile>$(Package_TestAssets_Linux_x64_6_0)/WebApp3/SOS.WebApp3.Heap.dmp.xml</TestDataFile>
+    </Option>
   </Options>
 
   <!-- Linux arm64 testing -->
       <DumpFile>$(Package_TestAssets_Linux_arm64_5_0)/LineNums/SOS.LineNums.Heap.dmp</DumpFile>
       <TestDataFile>$(Package_TestAssets_Linux_arm64_5_0)/LineNums/SOS.LineNums.Heap.dmp.xml</TestDataFile>
     </Option>
+    <Option>
+      <DumpFile>$(Package_TestAssets_Linux_arm64_6_0)/DivZero/SOS.DivZero.Heap.dmp</DumpFile>
+      <TestDataFile>$(Package_TestAssets_Linux_arm64_6_0)/DivZero/SOS.DivZero.Heap.dmp.xml</TestDataFile>
+    </Option>
+    <Option>
+      <DumpFile>$(Package_TestAssets_Linux_arm64_6_0)/DivZero/SOS.DivZero.Triage.dmp</DumpFile>
+      <TestDataFile>$(Package_TestAssets_Linux_arm64_6_0)/DivZero/SOS.DivZero.Triage.dmp.xml</TestDataFile>
+    </Option>
+    <Option>
+      <DumpFile>$(Package_TestAssets_Linux_arm64_6_0)/WebApp3/SOS.WebApp3.Heap.dmp</DumpFile>
+      <TestDataFile>$(Package_TestAssets_Linux_arm64_6_0)/WebApp3/SOS.WebApp3.Heap.dmp.xml</TestDataFile>
+    </Option>
   </Options>
   
   <!-- Run tests on OSX x64 -->
index b744b86dfc6fc0f6e96ebf461426d915cd133579..fedc6f7f776368f9631d400f7265fd9c71b9c8ef 100644 (file)
@@ -1,5 +1,4 @@
-using Microsoft.Diagnostics.Repl;
-using Microsoft.Diagnostics.Runtime;
+using Microsoft.Diagnostics.Runtime;
 using Microsoft.Diagnostics.TestHelpers;
 using System;
 using System.Collections.Generic;
@@ -25,22 +24,29 @@ namespace Microsoft.Diagnostics.DebugServices.UnitTests
         {
             _configurations ??= TestRunConfiguration.Instance.Configurations
                 .Where((config) => config.AllSettings.ContainsKey("DumpFile"))
-                .Select((config) => TestHost.CreateHost(config))
+                .Select((config) => CreateHost(config))
                 .Select((host) => new[] { host }).ToImmutableArray();
             return _configurations;
         }
 
+        private static TestHost CreateHost(TestConfiguration config)
+        {
+            if (config.IsTestDbgEng())
+            {
+                return new TestDbgEng(config);
+            }
+            else
+            {
+                return new TestDump(config);
+            }
+        }
+
         ITestOutputHelper Output { get; set; }
 
         public DebugServicesTests(ITestOutputHelper output)
         {
             Output = output;
-
-            if (Trace.Listeners[ListenerName] == null) 
-            {
-                Trace.Listeners.Add(new LoggingListener(output));
-                Trace.AutoFlush = true;
-            }
+            LoggingListener.EnableListener(output, ListenerName);
         }
 
         void IDisposable.Dispose() => Trace.Listeners.Remove(ListenerName);
@@ -273,28 +279,5 @@ namespace Microsoft.Diagnostics.DebugServices.UnitTests
                 }
             }
         }
-
-        class LoggingListener : TraceListener
-        {
-            private readonly CharToLineConverter _converter;
-
-            internal LoggingListener(ITestOutputHelper output)
-                : base(ListenerName)
-            {
-                _converter = new CharToLineConverter((text) => {
-                    output.WriteLine(text);
-                });
-            }
-
-            public override void Write(string message)
-            {
-                _converter.Input(message);
-            }
-
-            public override void WriteLine(string message)
-            {
-                _converter.Input(message + Environment.NewLine);
-            }
-        }
     }
 }
index a73c733627d22532fb000a70a906bda59c0b88af..d947ebb42e3ff1cca4833d7be51c180d990fd409 100644 (file)
@@ -3,7 +3,7 @@
   <PropertyGroup>
     <!-- Needs to be netcoreapp3.1 and not higher so this test assembly can be loaded by dotnet-dump via the DOTNET_DIAGNOSTIC_EXTENSIONS env var -->
     <TargetFramework>netcoreapp3.1</TargetFramework>
-    <TestConfigFileName>$(OutputPath)$(TargetFramework)\Debugger.Tests.Common.txt</TestConfigFileName>
+    <DebugServicesConfigFileName>$(OutputPath)$(TargetFramework)\Debugger.Tests.Common.txt</DebugServicesConfigFileName>
     <TestAssetsVersion>1.0.257801</TestAssetsVersion>
     <!-- Controls the test asset package restore and the tests that use them -->
     <RunTests>true</RunTests>
@@ -18,7 +18,6 @@
     <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.DebugServices.Implementation\Microsoft.Diagnostics.DebugServices.Implementation.csproj" />
     <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.DebugServices\Microsoft.Diagnostics.DebugServices.csproj" />
     <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.TestHelpers\Microsoft.Diagnostics.TestHelpers.csproj" />
-    <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.Repl\Microsoft.Diagnostics.Repl.csproj" />
     <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\SOS\SOS.Extensions\SOS.Extensions.csproj" />
   </ItemGroup>
     
     </ConfigFileEntries>
   </ItemGroup>
 
-  <Target Name="WriteTestConfigFile" Outputs="$(TestConfigFileName)" AfterTargets="Build">
+  <Target Name="DebugServicesWriteTestConfigFile" Outputs="$(DebugServicesConfigFileName)" AfterTargets="Build">
      <PropertyGroup>
-       <TestConfigFileLines>@(ConfigFileEntries->Metadata("ConfigFileEntry"))</TestConfigFileLines>
+       <TestConfigFileLines>@(ConfigFileEntries-&gt;Metadata("ConfigFileEntry"))</TestConfigFileLines>
      </PropertyGroup>
-    <WriteLinesToFile File="$(TestConfigFileName)" Lines="$(TestConfigFileLines)" Overwrite="true" WriteOnlyWhenDifferent="true" />
-    <Message Importance="High" Text="Created config file $(TestConfigFileName)" />
+    <WriteLinesToFile File="$(DebugServicesConfigFileName)" Lines="$(TestConfigFileLines)" Overwrite="true" WriteOnlyWhenDifferent="true" />
+    <Message Importance="High" Text="Created config file $(DebugServicesConfigFileName)" />
     <ItemGroup>
-      <FileWrites Include="$(TestConfigFileName)" />
+      <FileWrites Include="$(DebugServicesConfigFileName)" />
     </ItemGroup>
   </Target>
 </Project>
diff --git a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestDataReader.cs b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestDataReader.cs
deleted file mode 100644 (file)
index f7af9dd..0000000
+++ /dev/null
@@ -1,325 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Diagnostics;
-using System.Globalization;
-using System.Linq;
-using System.Reflection;
-using System.Xml.Linq;
-using Xunit;
-
-namespace Microsoft.Diagnostics.DebugServices.UnitTests
-{
-    public class TestDataReader
-    {
-        /// <summary>
-        /// Test data value
-        /// </summary>
-        public class Value
-        {
-            private readonly object _value;
-
-            internal Value(string valueString)
-            {
-                _value = valueString;
-            }
-
-            internal Value(ImmutableArray<ImmutableDictionary<string, Value>> values)
-            {
-                _value = values;
-            }
-
-            /// <summary>
-            /// Returns true if sub values
-            /// </summary>
-            public bool IsSubValue => _value is ImmutableArray<ImmutableDictionary<string, Value>>;
-
-            /// <summary>
-            /// Return the sub values for nested test data
-            /// </summary>
-            public ImmutableArray<ImmutableDictionary<string, Value>> Values
-            {
-                get { return _value is ImmutableArray<ImmutableDictionary<string, Value>> values ? values : ImmutableArray<ImmutableDictionary<string, Value>>.Empty; }
-            }
-
-            /// <summary>
-            /// Get the test data value as type T
-            /// </summary>
-            public T GetValue<T>()
-            {
-                return (T)GetValue(typeof(T));
-            }
-
-            /// <summary>
-            /// Get the test data value as "type"
-            /// </summary>
-            public object GetValue(Type type)
-            {
-                object value = _value;
-                if (value is string valueString)
-                {
-                    GetValue(type, valueString, ref value);
-                }
-                return value;
-            }
-
-            /// <summary>
-            /// Convert test data string to value
-            /// </summary>
-            /// <param name="type">type to convert to</param>
-            /// <param name="valueString">test data string</param>
-            /// <param name="result">resulting object</param>
-            public static void GetValue(Type type, string valueString, ref object result)
-            {
-                valueString = valueString.Trim();
-                if (type == typeof(string))
-                {
-                    result = valueString ?? "";
-                }
-                else if (type == typeof(bool))
-                {
-                    switch (valueString.ToLowerInvariant())
-                    {
-                        case "true":
-                            result = true;
-                            break;
-                        case "false":
-                            result = false;
-                            break;
-                    }
-                }
-                else if (type.IsEnum)
-                {
-                    result = Enum.Parse(type, valueString);
-                }
-                else if (type.IsPrimitive)
-                {
-                    NumberStyles style = valueString.StartsWith("0x") ? NumberStyles.HexNumber : NumberStyles.Integer;
-                    if (ulong.TryParse(valueString.Replace("0x", ""), style, CultureInfo.InvariantCulture, out ulong integerValue))
-                    {
-                        result = Convert.ChangeType(integerValue, type);
-                    }
-                }
-            }
-        }
-
-        /// <summary>
-        /// Test data file version
-        /// </summary>
-        public readonly Version Version;
-
-        /// <summary>
-        /// Target test data
-        /// </summary>
-        public readonly ImmutableDictionary<string, Value> Target;
-
-        /// <summary>
-        /// Shortcut to the module test data
-        /// </summary>
-        public readonly ImmutableArray<ImmutableDictionary<string, Value>> Modules;
-
-        /// <summary>
-        /// Shortcut  to the thread test data
-        /// </summary>
-        public readonly ImmutableArray<ImmutableDictionary<string, Value>> Threads;
-
-        /// <summary>
-        /// Shortcut to the runtime test data
-        /// </summary>
-        public readonly ImmutableArray<ImmutableDictionary<string, Value>> Runtimes;
-
-        /// <summary>
-        /// Open the test data xml file
-        /// </summary>
-        /// <param name="testDataFile"></param>
-        public TestDataReader(string testDataFile)
-        {
-            XDocument doc = XDocument.Load(testDataFile);
-            XElement root = doc.Root;
-            Assert.Equal("TestData", root.Name);
-            foreach (XElement child in root.Elements())
-            {
-                switch (child.Name.LocalName)
-                {
-                    case "Version":
-                        Version = Version.Parse(child.Value);
-                        break;
-                    case "Target":
-                        Target = Build(child);
-                        break;
-                }
-            }
-            Modules = Target["Modules"].Values;
-            Threads = Target["Threads"].Values;
-            Runtimes = Target["Runtimes"].Values;
-        }
-
-        private static ImmutableDictionary<string, Value> Build(XElement node)
-        {
-            var members = new Dictionary<string, Value>();
-            foreach (XElement dataNode in node.Elements())
-            {
-                string name = dataNode.Name.LocalName;
-                if (dataNode.HasElements)
-                {
-                    var items = new List<ImmutableDictionary<string, Value>>();
-                    foreach (XElement subValue in dataNode.Elements())
-                    {
-                        if (subValue.HasElements)
-                        {
-                            // Has multiple elements (i.e. Modules, Threads, Runtimes, 
-                            // etc). Assumes the same name for each entry.
-                            items.Add(Build(subValue));
-                        }
-                        else
-                        {
-                            // Only has sub members (i.e. RuntimeModule, etc.)
-                            items.Add(Build(dataNode));
-                            break;
-                        }
-                    }
-                    members.Add(name, new Value(items.ToImmutableArray()));
-                }
-                else
-                {
-                    members.Add(name, new Value(dataNode.Value));
-                }
-            }
-            return members.ToImmutableDictionary();
-        }
-    }
-
-    public static class TestDataExtensions
-    {
-        /// <summary>
-        /// Helper function to get a test data value 
-        /// </summary>
-        /// <typeparam name="T">type to convert test data value</typeparam>
-        /// <param name="values">values collection to lookup name</param>
-        /// <param name="name">value name</param>
-        /// <param name="value">result value of type T</param>
-        /// <returns></returns>
-        public static bool TryGetValue<T>(
-            this ImmutableDictionary<string, TestDataReader.Value> values, string name, out T value)
-        {
-            if (values.TryGetValue(name, out TestDataReader.Value testValue))
-            {
-                value = testValue.GetValue<T>();
-                return true;
-            }
-            value = default;
-            return false;
-        }
-
-        /// <summary>
-        /// Finds the match item (i.e. IModule, IThread, etc.) in the test data.
-        /// </summary>
-        /// <typeparam name="T">field or property type</typeparam>
-        /// <param name="items">Modules, Threads, Registers, etc. test data</param>
-        /// <param name="propety">name of property to use for search</param>
-        /// <param name="propertyValue"></param>
-        /// <returns>test data values</returns>
-        public static ImmutableDictionary<string, TestDataReader.Value> Find<T>(
-            this ImmutableArray<ImmutableDictionary<string, TestDataReader.Value>> items, string propety, T propertyValue)
-            where T : IComparable
-        {
-            foreach (var item in items)
-            {
-                TestDataReader.Value value = item[propety];
-                if (propertyValue.CompareTo(value.GetValue<T>()) == 0)
-                {
-                    return item;
-                }
-            }
-            return default;
-        }
-
-        /// <summary>
-        /// Compares the test data values with the properties in the instance with the same name. This is
-        /// used to compare ITarget, IModule, IThread, RegiserInfo instances to the test data.
-        /// </summary>
-        /// <param name="values">test data for the item</param>
-        /// <param name="instance">object to compare</param>
-        public static void CompareMembers(
-            this ImmutableDictionary<string, TestDataReader.Value> values, object instance)
-        {
-            foreach (KeyValuePair<string, TestDataReader.Value> testData in values)
-            {
-                MemberInfo[] members = instance.GetType().GetMember(
-                    testData.Key,
-                    MemberTypes.Field | MemberTypes.Property | MemberTypes.Method,
-                    BindingFlags.Public | BindingFlags.Instance);
-
-                if (members.Length > 0)
-                {
-                    MemberInfo member = members.Single();
-                    object memberValue = null;
-                    Type memberType = null;
-
-                    switch (member.MemberType)
-                    {
-                        case MemberTypes.Property:
-                            memberValue = ((PropertyInfo)member).GetValue(instance);
-                            memberType = ((PropertyInfo)member).PropertyType;
-                            break;
-                        case MemberTypes.Field:
-                            memberValue = ((FieldInfo)member).GetValue(instance);
-                            memberType = ((FieldInfo)member).FieldType;
-                            break;
-                        case MemberTypes.Method:
-                            if (((MethodInfo)member).GetParameters().Length == 0)
-                            {
-                                memberValue = ((MethodInfo)member).Invoke(instance, null);
-                                memberType = ((MethodInfo)member).ReturnType;
-                            }
-                            break;
-                    }
-                    if (memberType != null)
-                    {
-                        if (testData.Value.IsSubValue)
-                        {
-                            Trace.TraceInformation($"CompareMembers {testData.Key} sub value:");
-                            CompareMembers(testData.Value.Values.Single(), memberValue);
-                        }
-                        else
-                        {
-                            Type nullableType = Nullable.GetUnderlyingType(memberType);
-                            memberType = nullableType ?? memberType;
-
-                            if (nullableType != null && memberValue == null)
-                            {
-                                memberValue = string.Empty;
-                            }
-                            else if (memberType == typeof(string))
-                            {
-                                memberValue ??= string.Empty;
-                            }
-                            else if (memberValue is ImmutableArray<byte> buildId)
-                            {
-                                memberType = typeof(string);
-                                memberValue = !buildId.IsDefaultOrEmpty ? string.Concat(buildId.Select((b) => b.ToString("x2"))) : string.Empty;
-                            }
-                            else if (!memberType.IsPrimitive && !memberType.IsEnum)
-                            {
-                                memberType = typeof(string);
-                                memberValue = memberValue?.ToString() ?? string.Empty;
-                            }
-                            object testDataValue = testData.Value.GetValue(memberType);
-                            Trace.TraceInformation($"CompareMembers {testData.Key}: '{memberValue}' == '{testDataValue}'");
-                            Assert.Equal(memberValue, testDataValue);
-                        }
-                    }
-                    else 
-                    { 
-                        Trace.TraceWarning($"CompareMembers {testData.Key} member not found");
-                        return;
-                    }
-                }
-                else
-                {
-                    Trace.TraceWarning($"CompareMembers {testData.Key} not found");
-                }
-            }
-        }
-    }
-}
diff --git a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestDataWriter.cs b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestDataWriter.cs
deleted file mode 100644 (file)
index 867028f..0000000
+++ /dev/null
@@ -1,261 +0,0 @@
-using Microsoft.Diagnostics.DebugServices;
-using System;
-using System.Collections.Immutable;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Runtime.InteropServices;
-using System.Xml.Linq;
-
-namespace Microsoft.Diagnostics.DebugServices.UnitTests
-{
-    public class TestDataWriter
-    {
-        public readonly XElement Root;
-        public readonly XElement Target;
-
-        /// <summary>
-        /// Write a test data file from the target
-        /// </summary>
-        public TestDataWriter()
-        {
-            Root = new XElement("TestData");
-            Root.Add(new XElement("Version", "1.0.0"));
-            Target = new XElement("Target");
-            Root.Add(Target);
-        }
-
-        public void Build(ITarget target)
-        {
-            AddMembers(Target, typeof(ITarget), target, nameof(ITarget.Id), nameof(ITarget.GetTempDirectory));
-
-            var modulesElement = new XElement("Modules");
-            Target.Add(modulesElement);
-
-            var moduleService = target.Services.GetService<IModuleService>();
-            string runtimeModuleName = target.GetPlatformModuleName("coreclr");
-            foreach (IModule module in moduleService.EnumerateModules())
-            {
-                var moduleElement = new XElement("Module");
-                modulesElement.Add(moduleElement);
-                AddModuleMembers(moduleElement, module, runtimeModuleName);
-            }
-
-            var threadsElement = new XElement("Threads");
-            Target.Add(threadsElement);
-
-            var threadService = target.Services.GetService<IThreadService>();
-            var registerIndexes = new int[] { threadService.InstructionPointerIndex, threadService.StackPointerIndex, threadService.FramePointerIndex };
-            foreach (IThread thread in threadService.EnumerateThreads())
-            {
-                var threadElement = new XElement("Thread");
-                threadsElement.Add(threadElement);
-                AddMembers(threadElement, typeof(IThread), thread, nameof(IThread.ThreadIndex), nameof(IThread.GetThreadContext));
-
-                var registersElement = new XElement("Registers");
-                threadElement.Add(registersElement);
-                foreach (int registerIndex in registerIndexes)
-                {
-                    var registerElement = new XElement("Register");
-                    registersElement.Add(registerElement);
-
-                    if (threadService.TryGetRegisterInfo(registerIndex, out RegisterInfo info))
-                    {
-                        AddMembers(registerElement, typeof(RegisterInfo), info, nameof(Object.ToString), nameof(Object.GetHashCode));
-                    }
-                    if (thread.TryGetRegisterValue(registerIndex, out ulong value))
-                    {
-                        registerElement.Add(new XElement("Value", $"0x{value:X16}"));
-                    }
-                }
-            }
-
-            var runtimesElement = new XElement("Runtimes");
-            Target.Add(runtimesElement);
-
-            var runtimeService = target.Services.GetService<IRuntimeService>();
-            foreach (IRuntime runtime in runtimeService.EnumerateRuntimes())
-            {
-                var runtimeElement = new XElement("Runtime");
-                runtimesElement.Add(runtimeElement);
-                AddMembers(runtimeElement, typeof(IRuntime), runtime, nameof(IRuntime.GetDacFilePath), nameof(IRuntime.GetDbiFilePath));
-
-                var runtimeModuleElement = new XElement("RuntimeModule");
-                runtimeElement.Add(runtimeModuleElement);
-                AddModuleMembers(runtimeModuleElement, runtime.RuntimeModule, symbolModuleName: null);
-            }
-        }
-
-        public void Write(string testDataFile)
-        {
-            File.WriteAllText(testDataFile, Root.ToString());
-        }
-
-        private void AddModuleMembers(XElement element, IModule module, string symbolModuleName)
-        {
-            AddMembers(element, typeof(IModule), module, nameof(IModule.ModuleIndex), nameof(IModule.PdbFileInfos), nameof(IModule.VersionString));
-
-            if (symbolModuleName != null && IsModuleEqual(module, symbolModuleName))
-            {
-                IExportSymbols exportSymbols = module.Services.GetService<IExportSymbols>();
-                if (exportSymbols is not null)
-                {
-                    XElement exportSymbolsElement = null;
-
-                    string symbol1 = "coreclr_initialize";
-                    if (exportSymbols.TryGetSymbolAddress(symbol1, out ulong offset1))
-                    {
-                        XElement symbolElement = AddExportSymbolSection();
-                        symbolElement.Add(new XElement("Name", symbol1));
-                        symbolElement.Add(new XElement("Value", ToHex(offset1)));
-                    }
-                    string symbol2 = "coreclr_execute_assembly";
-                    if (exportSymbols.TryGetSymbolAddress(symbol2, out ulong offset2))
-                    {
-                        XElement symbolElement = AddExportSymbolSection();
-                        symbolElement.Add(new XElement("Name", symbol2));
-                        symbolElement.Add(new XElement("Value", ToHex(offset2)));
-                    }
-
-                    XElement AddExportSymbolSection()
-                    {
-                        if (exportSymbolsElement == null)
-                        {
-                            exportSymbolsElement = new XElement("ExportSymbols");
-                            element.Add(exportSymbolsElement);
-                        }
-                        var symbolElement = new XElement("Symbol");
-                        exportSymbolsElement.Add(symbolElement);
-                        return symbolElement;
-                    }
-                }
-
-                IModuleSymbols moduleSymbols = module.Services.GetService<IModuleSymbols>();
-                if (moduleSymbols is not null)
-                {
-                    XElement symbolsElement = null;
-
-                    string symbol1 = "coreclr_initialize";
-                    if (moduleSymbols.TryGetSymbolAddress(symbol1, out ulong offset1))
-                    {
-                        XElement symbolElement = AddExportSymbolSection();
-                        symbolElement.Add(new XElement("Name", symbol1));
-                        symbolElement.Add(new XElement("Value", ToHex(offset1)));
-                        symbolElement.Add(new XElement("Displacement", "0"));
-                    }
-
-                    XElement AddExportSymbolSection()
-                    {
-                        if (symbolsElement == null)
-                        {
-                            symbolsElement = new XElement("Symbols");
-                            element.Add(symbolsElement);
-                        }
-                        var symbolElement = new XElement("Symbol");
-                        symbolsElement.Add(symbolElement);
-                        return symbolElement;
-                    }
-                }
-            }
-        }
-
-        private void AddMembers(XElement element, Type type, object instance, params string[] membersToSkip)
-        {
-            MemberInfo[] members = type.GetMembers(BindingFlags.Public | BindingFlags.Instance);
-            foreach (MemberInfo member in members)
-            {
-                if (membersToSkip.Any((skip) => member.Name == skip)) {
-                    continue;
-                }
-                string result = null;
-                object memberValue = null;
-                Type memberType = null;
-
-                switch (member.MemberType)
-                {
-                    case MemberTypes.Property:
-                        memberValue = ((PropertyInfo)member).GetValue(instance);
-                        memberType = ((PropertyInfo)member).PropertyType;
-                        break;
-                    case MemberTypes.Field:
-                        memberValue = ((FieldInfo)member).GetValue(instance);
-                        memberType = ((FieldInfo)member).FieldType;
-                        break;
-                    case MemberTypes.Method:
-                        MethodInfo methodInfo = (MethodInfo)member;
-                        if (!methodInfo.IsSpecialName && methodInfo.GetParameters().Length == 0 && methodInfo.ReturnType != typeof(void))
-                        {
-                            memberValue = ((MethodInfo)member).Invoke(instance, null);
-                            memberType = ((MethodInfo)member).ReturnType;
-                        }
-                        break;
-                }
-                if (memberType != null)
-                {
-                    Type nullableType = Nullable.GetUnderlyingType(memberType);
-                    memberType = nullableType ?? memberType;
-
-                    if (nullableType != null && memberValue == null)
-                    {
-                        result = "";
-                    }
-                    else if (memberType == typeof(string))
-                    {
-                        result = (string)memberValue ?? "";
-                    }
-                    else if (memberType == typeof(bool))
-                    {
-                        result = (bool)memberValue ? "true" : "false";
-                    }
-                    else if (memberValue is ImmutableArray<byte> buildId)
-                    {
-                        if (!buildId.IsDefaultOrEmpty)
-                        {
-                            result = string.Concat(buildId.Select((b) => b.ToString("x2")));
-                        }
-                    }
-                    else if (memberType.IsEnum)
-                    {
-                        result = memberValue.ToString();
-                    }
-                    else if (memberType.IsPrimitive)
-                    {
-                        if (memberType == typeof(short) || memberType == typeof(int) || memberType == typeof(long))
-                        {
-                            result = memberValue.ToString();
-                        }
-                        else
-                        {
-                            int digits = Marshal.SizeOf(memberType) * 2;
-                            result = string.Format($"0x{{0:X{digits}}}", memberValue);
-                        }
-                    }
-                    else if (memberType.IsValueType || memberType == typeof(VersionData) || memberType == typeof(PdbFileInfo))
-                    {
-                        result = memberValue?.ToString();
-                    }
-                }
-                if (result != null)
-                {
-                    element.Add(new XElement(member.Name, result));
-                }
-            }
-        }
-
-        private string ToHex<T>(T value) where T : struct 
-        {
-            int digits = Marshal.SizeOf(typeof(T)) * 2;
-            return string.Format($"0x{{0:X{digits}}}", value);
-        }
-
-        private bool IsModuleEqual(IModule module, string moduleName)
-        {
-            if (module.Target.OperatingSystem == OSPlatform.Windows) {
-                return StringComparer.OrdinalIgnoreCase.Equals(Path.GetFileName(module.FileName), moduleName);
-            }
-            else {
-                return string.Equals(Path.GetFileName(module.FileName), moduleName);
-            }
-        }
-    }
-}
index cfad3897e8a64add2f3e58048af0357804975a20..253b57b42c8517a95db789139557b72ae3a5a7dd 100644 (file)
@@ -1,5 +1,4 @@
-using Microsoft.Diagnostics.Repl;
-using Microsoft.Diagnostics.Runtime;
+using Microsoft.Diagnostics.Runtime;
 using Microsoft.Diagnostics.Runtime.Interop;
 using Microsoft.Diagnostics.Runtime.Utilities;
 using Microsoft.Diagnostics.TestHelpers;
diff --git a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestDump.cs b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestDump.cs
deleted file mode 100644 (file)
index 769ebdc..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-using Microsoft.Diagnostics.DebugServices.Implementation;
-using Microsoft.Diagnostics.Runtime;
-using Microsoft.Diagnostics.TestHelpers;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Runtime.InteropServices;
-
-namespace Microsoft.Diagnostics.DebugServices.UnitTests
-{
-    public class TestDump : TestHost, IHost
-    {
-        private readonly ServiceProvider _serviceProvider;
-        private readonly ContextService _contextService;
-        private readonly SymbolService _symbolService;
-        private DataTarget _dataTarget;
-        private int _targetIdFactory;
-
-        public TestDump(TestConfiguration config)
-            : base(config)
-        {
-            _serviceProvider = new ServiceProvider();
-            _contextService = new ContextService(this);
-            _symbolService = new SymbolService(this);
-            _serviceProvider.AddService<IContextService>(_contextService);
-            _serviceProvider.AddService<ISymbolService>(_symbolService);
-
-            // Automatically enable symbol server support
-            _symbolService.AddSymbolServer(msdl: true, symweb: false, symbolServerPath: null, authToken: null, timeoutInMinutes: 0);
-            _symbolService.AddCachePath(_symbolService.DefaultSymbolCache);
-        }
-
-        protected override ITarget GetTarget()
-        {
-            _dataTarget = DataTarget.LoadDump(DumpFile);
-
-            OSPlatform targetPlatform = _dataTarget.DataReader.TargetPlatform;
-            if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
-                targetPlatform = OSPlatform.OSX;
-            }
-            _symbolService.AddDirectoryPath(Path.GetDirectoryName(DumpFile));
-            return new TargetFromDataReader(_dataTarget.DataReader, targetPlatform, this, _targetIdFactory++, DumpFile);
-        }
-
-        #region IHost
-
-        public IServiceEvent OnShutdownEvent { get; } = new ServiceEvent();
-
-        HostType IHost.HostType => HostType.DotnetDump;
-
-        IServiceProvider IHost.Services => _serviceProvider;
-
-        IEnumerable<ITarget> IHost.EnumerateTargets() => Target != null ? new ITarget[] { Target } : Array.Empty<ITarget>();
-
-        void IHost.DestroyTarget(ITarget target)
-        {
-            if (target == null) {
-                throw new ArgumentNullException(nameof(target));
-            }
-            if (target == Target)
-            {
-                _contextService.ClearCurrentTarget();
-                if (target is IDisposable disposable) {
-                    disposable.Dispose();
-                }
-            }
-        }
-
-        #endregion
-    }
-}
diff --git a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestHost.cs b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestHost.cs
deleted file mode 100644 (file)
index 74ec35b..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-using Microsoft.Diagnostics.TestHelpers;
-using System.IO;
-
-namespace Microsoft.Diagnostics.DebugServices.UnitTests
-{
-    public abstract class TestHost
-    {
-        private TestDataReader _testData;
-        private ITarget _target;
-
-        public readonly TestConfiguration Config;
-
-        public TestHost(TestConfiguration config)
-        {
-            Config = config;
-        }
-
-        public static TestHost CreateHost(TestConfiguration config)
-        {
-            if (config.IsTestDbgEng())
-            {
-                return new TestDbgEng(config);
-            }
-            else
-            {
-                return new TestDump(config);
-            }
-        }
-
-        public TestDataReader TestData
-        {
-            get
-            {
-                _testData ??= new TestDataReader(TestDataFile);
-                return _testData;
-            }
-        }
-
-        public ITarget Target
-        {
-            get
-            {
-                _target ??= GetTarget();
-                return _target;
-            }
-        }
-
-        public bool IsTestDbgEng => Config.AllSettings.TryGetValue("TestDbgEng", out string value) && value == "true";
-
-        protected abstract ITarget GetTarget();
-
-        public string DumpFile => TestConfiguration.MakeCanonicalPath(Config.AllSettings["DumpFile"]);
-
-        public string TestDataFile => TestConfiguration.MakeCanonicalPath(Config.AllSettings["TestDataFile"]);
-
-        public override string ToString() => DumpFile;
-    }
-
-    public static class TestHostExtensions
-    {
-        public static bool IsTestDbgEng(this TestConfiguration config) => config.AllSettings.TryGetValue("TestDbgEng", out string value) && value == "true";
-    }
-}
index bc809ab4c50e18a3c05378e3d5c5ddc6846a551b..3d3722e1884ecc36ae2e0c68335e072fd1837526 100644 (file)
@@ -2,7 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-using Microsoft.Diagnostics.DebugServices;
+using Microsoft.Diagnostics.TestHelpers;
 
 namespace Microsoft.Diagnostics.DebugServices.UnitTests
 {