From: Mike McLaughlin Date: Mon, 28 Mar 2022 20:40:48 +0000 (-0700) Subject: Add single file debugging support to dbgshim (#2941) X-Git-Tag: accepted/tizen/unified/20221103.165810~28^2^2~16 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=2e325a803b5fc17e29531fa62d822c5a9d580e57;p=platform%2Fcore%2Fdotnet%2Fdiagnostics.git Add single file debugging support to dbgshim (#2941) 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 --- diff --git a/diagnostics.sln b/diagnostics.sln index 3765a7faf..5f940760e 100644 --- a/diagnostics.sln +++ b/diagnostics.sln @@ -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} diff --git a/eng/InstallRuntimes.proj b/eng/InstallRuntimes.proj index 1cc189bb5..c11cd5f6a 100644 --- a/eng/InstallRuntimes.proj +++ b/eng/InstallRuntimes.proj @@ -1,5 +1,5 @@ - + + 6.0.3 1.0.317101 diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 9759f0bb7..6f6f369a6 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -1,21 +1,27 @@ - - - - $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) + $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) - + + AfterTargets="PostBuildEvent" + Condition="$(NeedsPublishing) == 'true'" + DependsOnTargets="$(_BeforePublishNoBuildTargets);$(_CorePublishTargets)" /> + + + + diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Module.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/Module.cs index 325705a4b..ec5c3f3a3 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Module.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Module.cs @@ -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; diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs index 480c587d4..746ce1ecb 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs @@ -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 /// version string or null 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 diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ClrModulesCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ClrModulesCommand.cs index 194e16825..435e38402 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/ClrModulesCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/ClrModulesCommand.cs @@ -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) { diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/Host/ModulesCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/Host/ModulesCommand.cs index 7047b8b78..3db956e32 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/Host/ModulesCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/Host/ModulesCommand.cs @@ -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) { diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/Host/RuntimesCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/Host/RuntimesCommand.cs index 3b73de48c..8b05aa50b 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/Host/RuntimesCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/Host/RuntimesCommand.cs @@ -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(); + 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(index + sizeof(byte), sizeof(uint)).ToArray(), 0); + uint fileSize = BitConverter.ToUInt32(new ReadOnlySpan(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(index + sizeof(byte), index[0]).ToArray().ToImmutableArray(); + return string.Format("BuildId {0}", ToHex(buildId)); + } + + private string ToHex(ImmutableArray 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 index 000000000..c567124d4 --- /dev/null +++ b/src/Microsoft.Diagnostics.TestHelpers/CharToLineConverter.cs @@ -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 m_callback; + readonly StringBuilder m_text = new StringBuilder(); + + public CharToLineConverter(Action 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(); + } + } +} diff --git a/src/Microsoft.Diagnostics.TestHelpers/ConsoleTestOutputHelper.cs b/src/Microsoft.Diagnostics.TestHelpers/ConsoleTestOutputHelper.cs index 288d90ec3..3dea8582d 100644 --- a/src/Microsoft.Diagnostics.TestHelpers/ConsoleTestOutputHelper.cs +++ b/src/Microsoft.Diagnostics.TestHelpers/ConsoleTestOutputHelper.cs @@ -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 diff --git a/src/Microsoft.Diagnostics.TestHelpers/DotNetBuildDebuggeeTestStep.cs b/src/Microsoft.Diagnostics.TestHelpers/DotNetBuildDebuggeeTestStep.cs index c8225d885..63ef96bbb 100644 --- a/src/Microsoft.Diagnostics.TestHelpers/DotNetBuildDebuggeeTestStep.cs +++ b/src/Microsoft.Diagnostics.TestHelpers/DotNetBuildDebuggeeTestStep.cs @@ -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 index 000000000..1360a26d7 --- /dev/null +++ b/src/Microsoft.Diagnostics.TestHelpers/LoggingListener.cs @@ -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); + } + } +} diff --git a/src/Microsoft.Diagnostics.TestHelpers/Microsoft.Diagnostics.TestHelpers.csproj b/src/Microsoft.Diagnostics.TestHelpers/Microsoft.Diagnostics.TestHelpers.csproj index 6f7dac27a..f2d83c5aa 100644 --- a/src/Microsoft.Diagnostics.TestHelpers/Microsoft.Diagnostics.TestHelpers.csproj +++ b/src/Microsoft.Diagnostics.TestHelpers/Microsoft.Diagnostics.TestHelpers.csproj @@ -13,7 +13,14 @@ + + + + + + + diff --git a/src/Microsoft.Diagnostics.TestHelpers/ProcessRunner.cs b/src/Microsoft.Diagnostics.TestHelpers/ProcessRunner.cs index 94dd27d5f..e5bca1393 100644 --- a/src/Microsoft.Diagnostics.TestHelpers/ProcessRunner.cs +++ b/src/Microsoft.Diagnostics.TestHelpers/ProcessRunner.cs @@ -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 index 000000000..184504e90 --- /dev/null +++ b/src/Microsoft.Diagnostics.TestHelpers/RemoteExecutorHelper.cs @@ -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 RemoteInvoke(ITestOutputHelper output, TestConfiguration config, TimeSpan timeout, string dumpPath, Func> 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"); + } + }); + } + } +} diff --git a/src/Microsoft.Diagnostics.TestHelpers/TestConfiguration.cs b/src/Microsoft.Diagnostics.TestHelpers/TestConfiguration.cs index e68dde4bf..ece382ad0 100644 --- a/src/Microsoft.Diagnostics.TestHelpers/TestConfiguration.cs +++ b/src/Microsoft.Diagnostics.TestHelpers/TestConfiguration.cs @@ -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[] ParseConfigFile(string path, Dictionary[] templates) { XDocument doc = XDocument.Load(path); @@ -388,6 +403,30 @@ namespace Microsoft.Diagnostics.TestHelpers _configStringView = GetStringViewWithVersion(RuntimeFrameworkVersion); } + public string Serialize() + { + List nodes = new(); + foreach (KeyValuePair 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 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 index 000000000..192fe06fb --- /dev/null +++ b/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestDataReader.cs @@ -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 + { + /// + /// Test data value + /// + public class Value + { + private readonly object _value; + + internal Value(string valueString) + { + _value = valueString; + } + + internal Value(ImmutableArray> values) + { + _value = values; + } + + /// + /// Returns true if sub values + /// + public bool IsSubValue => _value is ImmutableArray>; + + /// + /// Return the sub values for nested test data + /// + public ImmutableArray> Values + { + get { return _value is ImmutableArray> values ? values : ImmutableArray>.Empty; } + } + + /// + /// Get the test data value as type T + /// + public T GetValue() + { + return (T)GetValue(typeof(T)); + } + + /// + /// Get the test data value as "type" + /// + public object GetValue(Type type) + { + object value = _value; + if (value is string valueString) + { + GetValue(type, valueString, ref value); + } + return value; + } + + /// + /// Convert test data string to value + /// + /// type to convert to + /// test data string + /// resulting object + 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); + } + } + } + } + + /// + /// Test data file version + /// + public readonly Version Version; + + /// + /// Target test data + /// + public readonly ImmutableDictionary Target; + + /// + /// Shortcut to the module test data + /// + public readonly ImmutableArray> Modules; + + /// + /// Shortcut to the thread test data + /// + public readonly ImmutableArray> Threads; + + /// + /// Shortcut to the runtime test data + /// + public readonly ImmutableArray> Runtimes; + + /// + /// Open the test data xml file + /// + /// + 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 Build(XElement node) + { + var members = new Dictionary(); + foreach (XElement dataNode in node.Elements()) + { + string name = dataNode.Name.LocalName; + if (dataNode.HasElements) + { + var items = new List>(); + 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 + { + /// + /// Helper function to get a test data value + /// + /// type to convert test data value + /// values collection to lookup name + /// value name + /// result value of type T + /// + public static bool TryGetValue( + this ImmutableDictionary values, string name, out T value) + { + if (values.TryGetValue(name, out TestDataReader.Value testValue)) + { + value = testValue.GetValue(); + return true; + } + value = default; + return false; + } + + /// + /// Finds the match item (i.e. IModule, IThread, etc.) in the test data. + /// + /// field or property type + /// Modules, Threads, Registers, etc. test data + /// name of property to use for search + /// + /// test data values + public static ImmutableDictionary Find( + this ImmutableArray> items, string propety, T propertyValue) + where T : IComparable + { + foreach (var item in items) + { + TestDataReader.Value value = item[propety]; + if (propertyValue.CompareTo(value.GetValue()) == 0) + { + return item; + } + } + return default; + } + + /// + /// 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. + /// + /// test data for the item + /// object to compare + public static void CompareMembers( + this ImmutableDictionary values, object instance) + { + foreach (KeyValuePair 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 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 index 000000000..5e6b284aa --- /dev/null +++ b/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestDataWriter.cs @@ -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; + + /// + /// Write a test data file from the target + /// + 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(); + 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(); + 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(); + 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(); + 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(); + 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 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 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 index 000000000..33a64ddb8 --- /dev/null +++ b/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestDump.cs @@ -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(_contextService); + _serviceProvider.AddService(_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 IHost.EnumerateTargets() => Target != null ? new ITarget[] { Target } : Array.Empty(); + + 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 index 000000000..92ba06039 --- /dev/null +++ b/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestHost.cs @@ -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"; + } +} diff --git a/src/SOS/SOS.Extensions/DebuggerServices.cs b/src/SOS/SOS.Extensions/DebuggerServices.cs index a9fd5dc9f..d969df20e 100644 --- a/src/SOS/SOS.Extensions/DebuggerServices.cs +++ b/src/SOS/SOS.Extensions/DebuggerServices.cs @@ -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(_vtable); + private ref readonly IDebuggerServicesVTable VTable => ref Unsafe.AsRef(_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] GetOperatingSystem; - public readonly delegate* unmanaged[Stdcall] GetDebuggeeType; - public readonly delegate* unmanaged[Stdcall] GetExecutingProcessorType; - public readonly delegate* unmanaged[Stdcall] AddCommand; - public readonly delegate* unmanaged[Stdcall] OutputString; - public readonly delegate* unmanaged[Stdcall] ReadVirtual; - public readonly delegate* unmanaged[Stdcall] WriteVirtual; - public readonly delegate* unmanaged[Stdcall] GetNumberModules; - public readonly delegate* unmanaged[Stdcall] GetModuleNames; - public readonly delegate* unmanaged[Stdcall] GetModuleInfo; - public readonly delegate* unmanaged[Stdcall] GetModuleVersionInformation; - public readonly delegate* unmanaged[Stdcall] GetNumberThreads; - public readonly delegate* unmanaged[Stdcall] GetThreadIdsByIndex; - public readonly delegate* unmanaged[Stdcall] GetThreadContextBySystemId; - public readonly delegate* unmanaged[Stdcall] GetCurrentProcessSystemId; - public readonly delegate* unmanaged[Stdcall] GetCurrentThreadSystemId; - public readonly delegate* unmanaged[Stdcall] SetCurrentThreadSystemId; - public readonly delegate* unmanaged[Stdcall] GetThreadTeb; - public readonly delegate* unmanaged[Stdcall] VirtualUnwind; - public readonly delegate* unmanaged[Stdcall] GetSymbolPath; - public readonly delegate* unmanaged[Stdcall] GetSymbolByOffset; - public readonly delegate* unmanaged[Stdcall] GetOffsetBySymbol; + [StructLayout(LayoutKind.Sequential)] + private readonly unsafe struct IDebuggerServicesVTable + { + public readonly delegate* unmanaged[Stdcall] GetOperatingSystem; + public readonly delegate* unmanaged[Stdcall] GetDebuggeeType; + public readonly delegate* unmanaged[Stdcall] GetExecutingProcessorType; + public readonly delegate* unmanaged[Stdcall] AddCommand; + public readonly delegate* unmanaged[Stdcall] OutputString; + public readonly delegate* unmanaged[Stdcall] ReadVirtual; + public readonly delegate* unmanaged[Stdcall] WriteVirtual; + public readonly delegate* unmanaged[Stdcall] GetNumberModules; + public readonly delegate* unmanaged[Stdcall] GetModuleNames; + public readonly delegate* unmanaged[Stdcall] GetModuleInfo; + public readonly delegate* unmanaged[Stdcall] GetModuleVersionInformation; + public readonly delegate* unmanaged[Stdcall] GetNumberThreads; + public readonly delegate* unmanaged[Stdcall] GetThreadIdsByIndex; + public readonly delegate* unmanaged[Stdcall] GetThreadContextBySystemId; + public readonly delegate* unmanaged[Stdcall] GetCurrentProcessSystemId; + public readonly delegate* unmanaged[Stdcall] GetCurrentThreadSystemId; + public readonly delegate* unmanaged[Stdcall] SetCurrentThreadSystemId; + public readonly delegate* unmanaged[Stdcall] GetThreadTeb; + public readonly delegate* unmanaged[Stdcall] VirtualUnwind; + public readonly delegate* unmanaged[Stdcall] GetSymbolPath; + public readonly delegate* unmanaged[Stdcall] GetSymbolByOffset; + public readonly delegate* unmanaged[Stdcall] GetOffsetBySymbol; + } } } diff --git a/src/SOS/SOS.Extensions/RemoteMemoryService.cs b/src/SOS/SOS.Extensions/RemoteMemoryService.cs index 0f4af6ed6..70aa7598e 100644 --- a/src/SOS/SOS.Extensions/RemoteMemoryService.cs +++ b/src/SOS/SOS.Extensions/RemoteMemoryService.cs @@ -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(_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] AllocVirtual; - public readonly delegate* unmanaged[Stdcall] FreeVirtual; + [StructLayout(LayoutKind.Sequential)] + private readonly unsafe struct IRemoteMemoryServiceVTable + { + public readonly delegate* unmanaged[Stdcall] AllocVirtual; + public readonly delegate* unmanaged[Stdcall] FreeVirtual; + } } } diff --git a/src/SOS/SOS.Hosting/CorDebugDataTargetWrapper.cs b/src/SOS/SOS.Hosting/CorDebugDataTargetWrapper.cs index 9139b399c..0954470bc 100644 --- a/src/SOS/SOS.Hosting/CorDebugDataTargetWrapper.cs +++ b/src/SOS/SOS.Hosting/CorDebugDataTargetWrapper.cs @@ -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(); diff --git a/src/SOS/SOS.Hosting/RuntimeWrapper.cs b/src/SOS/SOS.Hosting/RuntimeWrapper.cs index 62928a90d..c10e74136 100644 --- a/src/SOS/SOS.Hosting/RuntimeWrapper.cs +++ b/src/SOS/SOS.Hosting/RuntimeWrapper.cs @@ -14,7 +14,7 @@ using System.Text; namespace SOS.Hosting { - internal sealed unsafe class RuntimeWrapper : COMCallableIUnknown + public sealed unsafe class RuntimeWrapper : COMCallableIUnknown { /// /// 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); diff --git a/src/SOS/SOS.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt b/src/SOS/SOS.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt index ca58c841a..38cab1529 100644 --- a/src/SOS/SOS.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt +++ b/src/SOS/SOS.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt @@ -21,9 +21,6 @@ $(RootBinDir)/TestResults/$(TargetConfiguration)/sos.unittests_$(Timestamp) $(RootBinDir)/tmp/$(TargetConfiguration)\dumps - linux - osx - true false @@ -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. --> -ms - $(OSRid)-$(TargetArchitecture) + $(TargetRid) -ms - win-$(TargetArchitecture) + $(TargetRid) 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; diff --git a/src/dbgshim/dbgshim.h b/src/dbgshim/dbgshim.h index 004fd2a38..34c71e515 100644 --- a/src/dbgshim/dbgshim.h +++ b/src/dbgshim/dbgshim.h @@ -6,6 +6,7 @@ //***************************************************************************** #include +#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); diff --git a/src/dbgshim/dbgshim.ntdef b/src/dbgshim/dbgshim.ntdef index 2e254ab9d..8b6572e1f 100644 --- a/src/dbgshim/dbgshim.ntdef +++ b/src/dbgshim/dbgshim.ntdef @@ -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 diff --git a/src/dbgshim/dbgshim.vcxproj b/src/dbgshim/dbgshim.vcxproj index 189d87dac..6cc64432f 100644 --- a/src/dbgshim/dbgshim.vcxproj +++ b/src/dbgshim/dbgshim.vcxproj @@ -87,7 +87,7 @@ - $(RepoRoot)artifacts\obj;$(RepoRoot)src;$(RepoRoot)src\pal\prebuilt\inc;$(RepoRoot)src\inc;%(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) %(AdditionalOptions) /guard:ehcont /Zm200 /Zc:strictStrings /w34092 /w34121 /w34125 /w34130 /w34132 /w34212 /w34530 /w35038 /w44177 /ZH:SHA_256 /source-charset:utf-8 /homeparams $(IntDir) EnableFastChecks @@ -118,6 +118,7 @@ Level3 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) $(IntDir) + true 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) @@ -151,6 +152,9 @@ false + + true + @@ -382,10 +386,11 @@ + - + \ No newline at end of file diff --git a/src/dbgshim/dbgshim.vcxproj.filters b/src/dbgshim/dbgshim.vcxproj.filters index aa9a2523c..9d140cdfe 100644 --- a/src/dbgshim/dbgshim.vcxproj.filters +++ b/src/dbgshim/dbgshim.vcxproj.filters @@ -61,5 +61,6 @@ + \ No newline at end of file diff --git a/src/dbgshim/dbgshim_unixexports.src b/src/dbgshim/dbgshim_unixexports.src index b1daf52e5..fae2869f3 100644 --- a/src/dbgshim/dbgshim_unixexports.src +++ b/src/dbgshim/dbgshim_unixexports.src @@ -6,6 +6,7 @@ ResumeProcess CloseResumeHandle RegisterForRuntimeStartup RegisterForRuntimeStartupEx +RegisterForRuntimeStartup3 UnregisterForRuntimeStartup GetStartupNotificationEvent EnumerateCLRs @@ -14,4 +15,5 @@ CreateVersionStringFromModule CreateDebuggingInterfaceFromVersion CreateDebuggingInterfaceFromVersionEx CreateDebuggingInterfaceFromVersion2 +CreateDebuggingInterfaceFromVersion3 CLRCreateInstance diff --git a/src/dbgshim/debugshim.cpp b/src/dbgshim/debugshim.cpp index 3dcfbcc25..964c69ac3 100644 --- a/src/dbgshim/debugshim.cpp +++ b/src/dbgshim/debugshim.cpp @@ -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 pLibraryProvider3; + ReleaseHolder pLibraryProvider2; + ReleaseHolder 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; diff --git a/src/dbgshim/debugshim.h b/src/dbgshim/debugshim.h index 93df46386..2d5d556b0 100644 --- a/src/dbgshim/debugshim.h +++ b/src/dbgshim/debugshim.h @@ -12,13 +12,107 @@ #include "cor.h" #include "cordebug.h" +#include "sstring.h" #include #include +#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, diff --git a/src/shared/dbgutil/CMakeLists.txt b/src/shared/dbgutil/CMakeLists.txt index b11cea1d2..66b0091dd 100644 --- a/src/shared/dbgutil/CMakeLists.txt +++ b/src/shared/dbgutil/CMakeLists.txt @@ -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}) diff --git a/src/shared/dbgutil/dbgutil.vcxproj b/src/shared/dbgutil/dbgutil.vcxproj index fc9e9bb26..d2eb25746 100644 --- a/src/shared/dbgutil/dbgutil.vcxproj +++ b/src/shared/dbgutil/dbgutil.vcxproj @@ -24,9 +24,11 @@ + + {A9A7C879-C320-3327-BB84-16E1322E17AE} @@ -86,7 +88,7 @@ - ..\..\SOS\dbgutil;..\..\pal\prebuilt\inc;..\..\inc;%(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) %(AdditionalOptions) /d2Zi+ /Zm200 /ZH:SHA_256 /source-charset:utf-8 /homeparams Debug/ true @@ -118,6 +120,7 @@ Level3 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) $(IntDir) + true 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) @@ -139,6 +142,9 @@ %(AdditionalOptions) /machine:x64 /IGNORE:4221 + + true + @@ -316,4 +322,4 @@ - + \ No newline at end of file diff --git a/src/shared/dbgutil/elfreader.cpp b/src/shared/dbgutil/elfreader.cpp index 423c15b7b..0848b3f3f 100644 --- a/src/shared/dbgutil/elfreader.cpp +++ b/src/shared/dbgutil/elfreader.cpp @@ -2,10 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. #include +#include +#include +#include #define __STDC_FORMAT_MACROS #include -#include #include "elfreader.h" +#include "arrayholder.h" #define Elf_Ehdr ElfW(Ehdr) #define Elf_Phdr ElfW(Phdr) @@ -32,23 +35,193 @@ 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 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(address), reinterpret_cast(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 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(loadbias + ph.p_vaddr); + if (pdynamicAddr != nullptr) + { + if (m_isFileLayout) + { + *pdynamicAddr = reinterpret_cast(loadbias + ph.p_offset); + } + else + { + *pdynamicAddr = reinterpret_cast(loadbias + ph.p_vaddr); + } } break; } diff --git a/src/shared/dbgutil/elfreader.h b/src/shared/dbgutil/elfreader.h index 67ddc0d86..a30d880cc 100644 --- a/src/shared/dbgutil/elfreader.h +++ b/src/shared/dbgutil/elfreader.h @@ -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 index 000000000..a5b443478 --- /dev/null +++ b/src/shared/dbgutil/machoreader.cpp @@ -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 +#include +#include +#include +#define __STDC_FORMAT_MACROS +#include +#include +#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(address), reinterpret_cast(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 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 index 000000000..2492705c1 --- /dev/null +++ b/src/shared/dbgutil/machoreader.h @@ -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 +#include +#include +#include +#include +#include + +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 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, ...) { }; +}; diff --git a/src/shared/gcdump/gcdump.vcxproj b/src/shared/gcdump/gcdump.vcxproj index ef1524093..23d2a3ab9 100644 --- a/src/shared/gcdump/gcdump.vcxproj +++ b/src/shared/gcdump/gcdump.vcxproj @@ -83,7 +83,7 @@ - ..\..\SOS\dbgutil;..\..\pal\prebuilt\inc;..\..\inc;%(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) %(AdditionalOptions) /d2Zi+ /Zm200 /ZH:SHA_256 /source-charset:utf-8 /homeparams Debug/ true @@ -115,6 +115,7 @@ Level3 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) $(IntDir) + true 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) @@ -136,6 +137,9 @@ %(AdditionalOptions) /machine:x64 /IGNORE:4221 + + true + @@ -313,4 +317,4 @@ - + \ No newline at end of file diff --git a/src/shared/inc/CMakeLists.txt b/src/shared/inc/CMakeLists.txt index 677ffb3fe..057306a18 100644 --- a/src/shared/inc/CMakeLists.txt +++ b/src/shared/inc/CMakeLists.txt @@ -1,5 +1,6 @@ -set( CORGUIDS_IDL_SOURCES +set(CORGUIDS_IDL_SOURCES + metahost.idl cordebug.idl xcordebug.idl clrdata.idl diff --git a/src/shared/inc/llvm/ELF.h b/src/shared/inc/llvm/ELF.h index 5cb3f82da..d820a2826 100644 --- a/src/shared/inc/llvm/ELF.h +++ b/src/shared/inc/llvm/ELF.h @@ -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 diff --git a/src/shared/inc/metahost.idl b/src/shared/inc/metahost.idl index 5bc51f6d1..3b455b147 100644 --- a/src/shared/inc/metahost.idl +++ b/src/shared/inc/metahost.idl @@ -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. ** diff --git a/src/shared/inc/runtimeinfo.h b/src/shared/inc/runtimeinfo.h index a5484c6b8..ef493ff38 100644 --- a/src/shared/inc/runtimeinfo.h +++ b/src/shared/inc/runtimeinfo.h @@ -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; diff --git a/src/shared/pal/inc/pal.h b/src/shared/pal/inc/pal.h index 979c8cdcc..01e0851cc 100644 --- a/src/shared/pal/inc/pal.h +++ b/src/shared/pal/inc/pal.h @@ -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 index 000000000..285aa8792 --- /dev/null +++ b/src/shared/pal/prebuilt/idl/metahost_i.cpp @@ -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 +#include + +#ifdef _MIDL_USE_GUIDDEF_ + +#ifndef INITGUID +#define INITGUID +#include +#undef INITGUID +#else +#include +#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 + + + diff --git a/src/shared/pal/prebuilt/inc/metahost.h b/src/shared/pal/prebuilt/inc/metahost.h index aae25d70d..069d71da5 100644 --- a/src/shared/pal/prebuilt/inc/metahost.h +++ b/src/shared/pal/prebuilt/inc/metahost.h @@ -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__ diff --git a/src/shared/pal/src/include/pal/procobj.hpp b/src/shared/pal/src/include/pal/procobj.hpp index 32357c40b..4cf647e3a 100644 --- a/src/shared/pal/src/include/pal/procobj.hpp +++ b/src/shared/pal/src/include/pal/procobj.hpp @@ -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; } }; // diff --git a/src/shared/pal/src/thread/process.cpp b/src/shared/pal/src/thread/process.cpp index 294c12fc6..80de0941b 100644 --- a/src/shared/pal/src/thread/process.cpp +++ b/src/shared/pal/src/thread/process.cpp @@ -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; } diff --git a/src/shared/utilcode/utilcode.vcxproj b/src/shared/utilcode/utilcode.vcxproj index 19b19eeb5..0d53d34f2 100644 --- a/src/shared/utilcode/utilcode.vcxproj +++ b/src/shared/utilcode/utilcode.vcxproj @@ -79,7 +79,7 @@ - $(ArtifactsObjDir)\Windows_NT.x64.Debug\src\utilcode;$(RepoRoot)src\utilcode;$(ArtifactsObjDir);$(RepoRoot)src;$(RepoRoot)src\pal\prebuilt\inc;$(RepoRoot)src\inc;%(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) %(AdditionalOptions) /guard:ehcont /Zm200 /Zc:strictStrings /w34092 /w34121 /w34125 /w34130 /w34132 /w34212 /w34530 /w35038 /w44177 /ZH:SHA_256 /source-charset:utf-8 /homeparams $(IntDir) EnableFastChecks @@ -109,6 +109,7 @@ Level3 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) $(IntDir) + true 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) @@ -130,6 +131,9 @@ %(AdditionalOptions) /machine:x64 /IGNORE:4221 + + true + @@ -337,4 +341,4 @@ - + \ 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 index 000000000..e03b297ca --- /dev/null +++ b/src/tests/DbgShim.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt @@ -0,0 +1,113 @@ + + + $(RepoRootDir)/.dotnet-test + + + $(RepoRootDir)/artifacts + $(RootBinDir)/bin/$(OS).$(TargetArchitecture).$(TargetConfiguration) + $(RootBinDir)/TestResults/$(TargetConfiguration)/dbgshim.unittests_$(Timestamp) + + + net6.0 + + ProjectK + $(RepoRootDir)\src\tests\DbgShim.UnitTests\Debuggees + cli + SimpleDebuggee + + !-- Use the global.json SDK to build and the test SDK/runtime to run --> + + $(DotNetRoot)/dotnet + + + 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 + + + + false + true + false + + false + true + false + + + + + + + + + + + $(DotNetRoot)/dotnet + --fx-version $(RuntimeFrameworkVersion) + + $(DotNetRoot)/shared/Microsoft.NETCore.App/$(RuntimeFrameworkVersion) + + + + + + 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 index 000000000..6f23a07a9 --- /dev/null +++ b/src/tests/DbgShim.UnitTests/ConfigFiles/Windows/Debugger.Tests.Config.txt @@ -0,0 +1,98 @@ + + + $(RepoRootDir)\.dotnet-test + $(RepoRootDir)\.dotnet-test\x86 + + + $(RepoRootDir)\artifacts + $(RootBinDir)\bin\Windows_NT.$(TargetArchitecture).$(TargetConfiguration) + $(RootBinDir)\TestResults\$(TargetConfiguration)\dbgshim.unittests_$(Timestamp) + + + net6.0 + + ProjectK + $(RepoRootDir)\src\tests\DbgShim.UnitTests\Debuggees + cli + SimpleDebuggee + + $(DotNetRoot)\dotnet.exe + + + 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 + + + true + false + + true + false + + + + + + + + + + + $(DotNetRoot)\dotnet.exe + --fx-version $(RuntimeFrameworkVersion) + + $(DotNetRoot)\shared\Microsoft.NETCore.App\$(RuntimeFrameworkVersion) + + + $(InstallDir)\dbgshim.dll + $(RuntimeModuleDir)\coreclr.dll + $(RuntimeModuleDir)\mscordbi.dll + $(RuntimeModuleDir)\mscordaccore.dll + diff --git a/src/tests/DbgShim.UnitTests/DbgShim.UnitTests.csproj b/src/tests/DbgShim.UnitTests/DbgShim.UnitTests.csproj new file mode 100644 index 000000000..fd0d2069d --- /dev/null +++ b/src/tests/DbgShim.UnitTests/DbgShim.UnitTests.csproj @@ -0,0 +1,91 @@ + + + + net6.0 + true + $(OutputPath)$(TargetFramework)\Debugger.Tests.Common.txt + 1.0.257801 + + + + + + + + + + + + + + Debugger.Tests.Config.txt + Always + + + Debugger.Tests.Config.txt + Always + + + + + + + + + + + + + + + + + + + $(Configuration) + $(RepoRoot) + $(NuGetPackageRoot)testassets.windows.x64.6.0\$(TestAssetsVersion)\content + $(NuGetPackageRoot)testassets.windows.x86.6.0\$(TestAssetsVersion)\content + $(NuGetPackageRoot)testassets.linux.x64.6.0\$(TestAssetsVersion)\content + $(NuGetPackageRoot)testassets.linux.arm64.6.0\$(TestAssetsVersion)\content + +]]> + + + + + + + + + + $(Configuration) + $(RepoRoot) + $(NuGetPackageRoot)testassets.linux.x64.6.0/$(TestAssetsVersion)/content + $(NuGetPackageRoot)testassets.linux.arm64.6.0/$(TestAssetsVersion)/content + +]]> + + + + + + + + + + @(ConfigFileEntries->Metadata("ConfigFileEntry")) + + + + + + + + diff --git a/src/tests/DbgShim.UnitTests/DbgShimAPI.cs b/src/tests/DbgShim.UnitTests/DbgShimAPI.cs new file mode 100644 index 000000000..1eba03fe4 --- /dev/null +++ b/src/tests/DbgShim.UnitTests/DbgShimAPI.cs @@ -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("CreateProcessForLaunch"); + _resumeProcess = GetDelegateFunction("ResumeProcess"); + _closeResumeHandle = GetDelegateFunction("CloseResumeHandle"); + _registerForRuntimeStartup = GetDelegateFunction("RegisterForRuntimeStartup"); + _registerForRuntimeStartupEx = GetDelegateFunction("RegisterForRuntimeStartupEx"); + _registerForRuntimeStartup3 = GetDelegateFunction("RegisterForRuntimeStartup3", optional: true); + _unregisterForRuntimeStartup = GetDelegateFunction("UnregisterForRuntimeStartup"); + _enumerateCLRs = GetDelegateFunction("EnumerateCLRs"); + _closeCLREnumeration = GetDelegateFunction("CloseCLREnumeration"); + _createVersionStringFromModule = GetDelegateFunction("CreateVersionStringFromModule"); + _createDebuggingInterfaceFromVersion = GetDelegateFunction("CreateDebuggingInterfaceFromVersion"); + _createDebuggingInterfaceFromVersionEx = GetDelegateFunction("CreateDebuggingInterfaceFromVersionEx"); + _createDebuggingInterfaceFromVersion2 = GetDelegateFunction("CreateDebuggingInterfaceFromVersion2"); + _createDebuggingInterfaceFromVersion3 = GetDelegateFunction("CreateDebuggingInterfaceFromVersion3", optional: true); + _clrCreateInstance = GetDelegateFunction("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 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(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 index 000000000..9aed6ff30 --- /dev/null +++ b/src/tests/DbgShim.UnitTests/DbgShimTests.cs @@ -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 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 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); + + /// + /// Test RegisterForRuntimeStartup for launch + /// + [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; + }); + } + + /// + /// Test RegisterForRuntimeStartupEx for launch + /// + [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; + }); + } + + /// + /// Test RegisterForRuntimeStartup3 for launch + /// + [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; + }); + } + + /// + /// Test RegisterForRuntimeStartup for attach + /// + [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; + }); + } + + /// + /// Test RegisterForRuntimeStartupEx for attach + /// + [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; + }); + } + + /// + /// Test RegisterForRuntimeStartup3 for attach + /// + [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; + }); + } + + /// + /// Test EnumerateCLRs/CloseCLREnumeration + /// + [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; + }); + } + + /// + /// Test CreateVersionStringFromModule/CreateDebuggingInterfaceFromVersion + /// + [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; + }); + } + + /// + /// Test CreateVersionStringFromModule/CreateDebuggingInterfaceFromVersionEx + /// + [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; + }); + } + + /// + /// Test CreateVersionStringFromModule/CreateDebuggingInterfaceFromVersion2 + /// + [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; + }); + } + + /// + /// Test CreateVersionStringFromModule/CreateDebuggingInterfaceFromVersion3 + /// + [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(); + 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 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); + } + + /// + /// 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. + /// + /// test configuration + /// name of test for the log file + /// delegate to call in the remote process + private async Task RemoteInvoke(TestConfiguration config, string testName, Func> 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); + } + + /// + /// Used in the remote invoke delegate to deserialize the test configuration xml and setup test output logging. + /// + /// test configuration xml + /// test configuration instance + /// test output instance + 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.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 index 000000000..3ac8d3d4c --- /dev/null +++ b/src/tests/DbgShim.UnitTests/DebuggeeInfo.cs @@ -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 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 index 000000000..0e870ae3e --- /dev/null +++ b/src/tests/DbgShim.UnitTests/Debuggees/SimpleDebuggee/SimpleDebuggee.cs @@ -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 index 000000000..03cd2f5e0 --- /dev/null +++ b/src/tests/DbgShim.UnitTests/Debuggees/SimpleDebuggee/SimpleDebuggee.csproj @@ -0,0 +1,7 @@ + + + Exe + $(BuildProjectFramework) + $(BuildTargetFrameworks) + + 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 index 000000000..f022b7ffc --- /dev/null +++ b/src/tests/DbgShim.UnitTests/Debuggees/SimpleDebuggee/runtimeconfig.template.json @@ -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 index 000000000..cd2ca5e87 --- /dev/null +++ b/src/tests/DbgShim.UnitTests/ICLRDebugging.cs @@ -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(_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] OpenVirtualProcess; + } + } +} diff --git a/src/tests/DbgShim.UnitTests/ICorDebug.cs b/src/tests/DbgShim.UnitTests/ICorDebug.cs new file mode 100644 index 000000000..18a0fcfb4 --- /dev/null +++ b/src/tests/DbgShim.UnitTests/ICorDebug.cs @@ -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(_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] Initialize; + public readonly delegate* unmanaged[Stdcall] Terminate; + public readonly delegate* unmanaged[Stdcall] SetManangedHandler; + public readonly delegate* unmanaged[Stdcall] SetUnmanangedHandler; + public readonly delegate* unmanaged[Stdcall] CreateProcess; + public readonly delegate* unmanaged[Stdcall] DebugActiveProcess; + } + } +} diff --git a/src/tests/DbgShim.UnitTests/ICorDebugController.cs b/src/tests/DbgShim.UnitTests/ICorDebugController.cs new file mode 100644 index 000000000..042b013bc --- /dev/null +++ b/src/tests/DbgShim.UnitTests/ICorDebugController.cs @@ -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(_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] Stop; + public readonly delegate* unmanaged[Stdcall] Continue; + public readonly delegate* unmanaged[Stdcall] IsRunning_dummy; + public readonly delegate* unmanaged[Stdcall] HasQueuedCallbacks_dummy; + public readonly delegate* unmanaged[Stdcall] EnumerateThreads_dummy; + public readonly delegate* unmanaged[Stdcall] SetAllThreadsDebugState_dummy; + public readonly delegate* unmanaged[Stdcall] Detach; + public readonly delegate* unmanaged[Stdcall] Terminate; + } + } +} diff --git a/src/tests/DbgShim.UnitTests/LibraryProviderWrapper.cs b/src/tests/DbgShim.UnitTests/LibraryProviderWrapper.cs new file mode 100644 index 000000000..63e8541a3 --- /dev/null +++ b/src/tests/DbgShim.UnitTests/LibraryProviderWrapper.cs @@ -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 _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 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(); + string modulePath = null; + if (buildIdBytes != null && buildIdSize > 0) + { + Span span = new Span(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 expectedBuildId, byte[] actualBuildId) + { + if (expectedBuildId.Length > 0) + { + Assert.Equal(expectedBuildId, actualBuildId); + } + } + + private static ImmutableArray 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.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 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 index 000000000..75e9094ac --- /dev/null +++ b/src/tests/DbgShim.UnitTests/ManagedCallbackWrapper.cs @@ -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 + } +} diff --git a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt index 5514455b3..6302b9a60 100644 --- a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt +++ b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt @@ -36,6 +36,18 @@ $(Package_TestAssets_Linux_x64_5_0)/LineNums/SOS.LineNums.Heap.dmp $(Package_TestAssets_Linux_x64_5_0)/LineNums/SOS.LineNums.Heap.dmp.xml + + + @@ -52,6 +64,18 @@ $(Package_TestAssets_Linux_arm64_5_0)/LineNums/SOS.LineNums.Heap.dmp $(Package_TestAssets_Linux_arm64_5_0)/LineNums/SOS.LineNums.Heap.dmp.xml + + + diff --git a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/DebugServicesTests.cs b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/DebugServicesTests.cs index b744b86df..fedc6f7f7 100644 --- a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/DebugServicesTests.cs +++ b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/DebugServicesTests.cs @@ -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); - } - } } } diff --git a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/Microsoft.Diagnostics.DebugServices.UnitTests.csproj b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/Microsoft.Diagnostics.DebugServices.UnitTests.csproj index a73c73362..d947ebb42 100644 --- a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/Microsoft.Diagnostics.DebugServices.UnitTests.csproj +++ b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/Microsoft.Diagnostics.DebugServices.UnitTests.csproj @@ -3,7 +3,7 @@ netcoreapp3.1 - $(OutputPath)$(TargetFramework)\Debugger.Tests.Common.txt + $(OutputPath)$(TargetFramework)\Debugger.Tests.Common.txt 1.0.257801 true @@ -18,7 +18,6 @@ - @@ -111,14 +110,14 @@ - + - @(ConfigFileEntries->Metadata("ConfigFileEntry")) + @(ConfigFileEntries->Metadata("ConfigFileEntry")) - - + + - + diff --git a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestDataReader.cs b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestDataReader.cs deleted file mode 100644 index f7af9dd2d..000000000 --- a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestDataReader.cs +++ /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 - { - /// - /// Test data value - /// - public class Value - { - private readonly object _value; - - internal Value(string valueString) - { - _value = valueString; - } - - internal Value(ImmutableArray> values) - { - _value = values; - } - - /// - /// Returns true if sub values - /// - public bool IsSubValue => _value is ImmutableArray>; - - /// - /// Return the sub values for nested test data - /// - public ImmutableArray> Values - { - get { return _value is ImmutableArray> values ? values : ImmutableArray>.Empty; } - } - - /// - /// Get the test data value as type T - /// - public T GetValue() - { - return (T)GetValue(typeof(T)); - } - - /// - /// Get the test data value as "type" - /// - public object GetValue(Type type) - { - object value = _value; - if (value is string valueString) - { - GetValue(type, valueString, ref value); - } - return value; - } - - /// - /// Convert test data string to value - /// - /// type to convert to - /// test data string - /// resulting object - 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); - } - } - } - } - - /// - /// Test data file version - /// - public readonly Version Version; - - /// - /// Target test data - /// - public readonly ImmutableDictionary Target; - - /// - /// Shortcut to the module test data - /// - public readonly ImmutableArray> Modules; - - /// - /// Shortcut to the thread test data - /// - public readonly ImmutableArray> Threads; - - /// - /// Shortcut to the runtime test data - /// - public readonly ImmutableArray> Runtimes; - - /// - /// Open the test data xml file - /// - /// - 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 Build(XElement node) - { - var members = new Dictionary(); - foreach (XElement dataNode in node.Elements()) - { - string name = dataNode.Name.LocalName; - if (dataNode.HasElements) - { - var items = new List>(); - 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 - { - /// - /// Helper function to get a test data value - /// - /// type to convert test data value - /// values collection to lookup name - /// value name - /// result value of type T - /// - public static bool TryGetValue( - this ImmutableDictionary values, string name, out T value) - { - if (values.TryGetValue(name, out TestDataReader.Value testValue)) - { - value = testValue.GetValue(); - return true; - } - value = default; - return false; - } - - /// - /// Finds the match item (i.e. IModule, IThread, etc.) in the test data. - /// - /// field or property type - /// Modules, Threads, Registers, etc. test data - /// name of property to use for search - /// - /// test data values - public static ImmutableDictionary Find( - this ImmutableArray> items, string propety, T propertyValue) - where T : IComparable - { - foreach (var item in items) - { - TestDataReader.Value value = item[propety]; - if (propertyValue.CompareTo(value.GetValue()) == 0) - { - return item; - } - } - return default; - } - - /// - /// 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. - /// - /// test data for the item - /// object to compare - public static void CompareMembers( - this ImmutableDictionary values, object instance) - { - foreach (KeyValuePair 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 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 index 867028f53..000000000 --- a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestDataWriter.cs +++ /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; - - /// - /// Write a test data file from the target - /// - 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(); - 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(); - 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(); - 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(); - 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(); - 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 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 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/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestDbgEng.cs b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestDbgEng.cs index cfad3897e..253b57b42 100644 --- a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestDbgEng.cs +++ b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestDbgEng.cs @@ -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 index 769ebdcf0..000000000 --- a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestDump.cs +++ /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(_contextService); - _serviceProvider.AddService(_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 IHost.EnumerateTargets() => Target != null ? new ITarget[] { Target } : Array.Empty(); - - 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 index 74ec35baa..000000000 --- a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestHost.cs +++ /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"; - } -} diff --git a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/WriteTestData.cs b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/WriteTestData.cs index bc809ab4c..3d3722e18 100644 --- a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/WriteTestData.cs +++ b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/WriteTestData.cs @@ -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 {