From 4b4aad7217d3292650e77eec2cf4c198ea9c3b4b Mon Sep 17 00:00:00 2001 From: Jiyoung Yun Date: Wed, 23 Nov 2016 19:09:09 +0900 Subject: Imported Upstream version 1.1.0 --- src/vm/.gitmirror | 1 + src/vm/CMakeLists.txt | 467 + src/vm/ClrEtwAll.man | 7026 ++++++++ src/vm/ClrEtwAllMeta.lst | 607 + src/vm/amd64/.gitmirror | 1 + src/vm/amd64/AsmHelpers.asm | 764 + src/vm/amd64/AsmMacros.inc | 442 + src/vm/amd64/CLRErrorReporting.vrg | 5 + src/vm/amd64/CallDescrWorkerAMD64.asm | 132 + src/vm/amd64/ComCallPreStub.asm | 158 + src/vm/amd64/CrtHelpers.asm | 528 + src/vm/amd64/ExternalMethodFixupThunk.asm | 107 + src/vm/amd64/GenericComCallStubs.asm | 304 + src/vm/amd64/GenericComPlusCallStubs.asm | 148 + src/vm/amd64/InstantiatingStub.asm | 151 + src/vm/amd64/JitHelpers_Fast.asm | 1028 ++ src/vm/amd64/JitHelpers_FastWriteBarriers.asm | 345 + src/vm/amd64/JitHelpers_InlineGetAppDomain.asm | 123 + src/vm/amd64/JitHelpers_InlineGetThread.asm | 1335 ++ src/vm/amd64/JitHelpers_Slow.asm | 1830 +++ src/vm/amd64/PInvokeStubs.asm | 282 + src/vm/amd64/RedirectedHandledJITCase.asm | 239 + src/vm/amd64/RemotingThunksAMD64.asm | 303 + src/vm/amd64/ThePreStubAMD64.asm | 36 + src/vm/amd64/TlsGetters.asm | 120 + src/vm/amd64/UMThunkStub.asm | 618 + src/vm/amd64/VirtualCallStubAMD64.asm | 109 + src/vm/amd64/asmconstants.h | 702 + src/vm/amd64/calldescrworkeramd64.S | 165 + src/vm/amd64/cgenamd64.cpp | 1278 ++ src/vm/amd64/cgencpu.h | 562 + src/vm/amd64/crthelpers.S | 41 + src/vm/amd64/excepamd64.cpp | 585 + src/vm/amd64/excepcpu.h | 91 + src/vm/amd64/externalmethodfixupthunk.S | 98 + src/vm/amd64/getstate.S | 45 + src/vm/amd64/getstate.asm | 85 + src/vm/amd64/gmsamd64.cpp | 144 + src/vm/amd64/gmscpu.h | 186 + src/vm/amd64/jithelpers_fast.S | 473 + src/vm/amd64/jithelpers_fastwritebarriers.S | 319 + src/vm/amd64/jithelpers_slow.S | 113 + src/vm/amd64/jithelpersamd64.cpp | 48 + src/vm/amd64/jitinterfaceamd64.cpp | 655 + src/vm/amd64/pinvokestubs.S | 129 + src/vm/amd64/profiler.cpp | 367 + src/vm/amd64/remotingamd64.cpp | 672 + src/vm/amd64/stublinkeramd64.cpp | 8 + src/vm/amd64/stublinkeramd64.h | 10 + src/vm/amd64/theprestubamd64.S | 30 + src/vm/amd64/umthunkstub.S | 210 + src/vm/amd64/unixasmhelpers.S | 231 + src/vm/amd64/unixstubs.cpp | 90 + src/vm/amd64/virtualcallstubamd64.S | 109 + src/vm/amd64/virtualcallstubcpu.hpp | 790 + src/vm/appdomain.cpp | 14915 +++++++++++++++++ src/vm/appdomain.hpp | 5291 ++++++ src/vm/appdomain.inl | 323 + src/vm/appdomainconfigfactory.hpp | 240 + src/vm/appdomainhelper.cpp | 546 + src/vm/appdomainhelper.h | 371 + src/vm/appdomainnative.cpp | 1782 ++ src/vm/appdomainnative.hpp | 153 + src/vm/appdomainstack.cpp | 195 + src/vm/appdomainstack.h | 231 + src/vm/appdomainstack.inl | 443 + src/vm/appxutil.cpp | 242 + src/vm/appxutil.h | 31 + src/vm/aptca.cpp | 1363 ++ src/vm/aptca.h | 110 + src/vm/argdestination.h | 218 + src/vm/argslot.h | 43 + src/vm/arm/.gitmirror | 1 + src/vm/arm/CrtHelpers.asm | 162 + src/vm/arm/PInvokeStubs.asm | 142 + src/vm/arm/armsinglestepper.cpp | 1212 ++ src/vm/arm/asmconstants.h | 304 + src/vm/arm/asmhelpers.S | 1474 ++ src/vm/arm/asmhelpers.asm | 2727 ++++ src/vm/arm/asmmacros.h | 161 + src/vm/arm/cgencpu.h | 1338 ++ src/vm/arm/crthelpers.S | 59 + src/vm/arm/ehhelpers.S | 149 + src/vm/arm/ehhelpers.asm | 182 + src/vm/arm/exceparm.cpp | 144 + src/vm/arm/excepcpu.h | 51 + src/vm/arm/gmscpu.h | 175 + src/vm/arm/jithelpersarm.cpp | 51 + src/vm/arm/memcpy.S | 39 + src/vm/arm/memcpy.asm | 284 + src/vm/arm/memcpy_crt.asm | 1001 ++ src/vm/arm/patchedcode.S | 71 + src/vm/arm/patchedcode.asm | 606 + src/vm/arm/pinvokestubs.S | 106 + src/vm/arm/profiler.cpp | 358 + src/vm/arm/stubs.cpp | 3948 +++++ src/vm/arm/unixstubs.cpp | 28 + src/vm/arm/virtualcallstubcpu.hpp | 384 + src/vm/arm64/.gitmirror | 1 + src/vm/arm64/CallDescrWorkerARM64.asm | 138 + src/vm/arm64/PInvokeStubs.asm | 137 + src/vm/arm64/asmconstants.h | 171 + src/vm/arm64/asmhelpers.S | 836 + src/vm/arm64/asmhelpers.asm | 1314 ++ src/vm/arm64/asmmacros.h | 296 + src/vm/arm64/calldescrworkerarm64.S | 128 + src/vm/arm64/cgenarm64.cpp | 38 + src/vm/arm64/cgencpu.h | 742 + src/vm/arm64/crthelpers.S | 291 + src/vm/arm64/crthelpers.asm | 300 + src/vm/arm64/excepcpu.h | 51 + src/vm/arm64/gmscpu.h | 93 + src/vm/arm64/pinvokestubs.S | 124 + src/vm/arm64/stubs.cpp | 2167 +++ src/vm/arm64/unixstubs.cpp | 28 + src/vm/arm64/virtualcallstubcpu.hpp | 457 + src/vm/armsinglestepper.h | 152 + src/vm/array.cpp | 1434 ++ src/vm/array.h | 113 + src/vm/assembly.cpp | 5070 ++++++ src/vm/assembly.hpp | 1004 ++ src/vm/assemblyname.cpp | 313 + src/vm/assemblyname.hpp | 31 + src/vm/assemblynamelist.h | 110 + src/vm/assemblynamesconfigfactory.cpp | 264 + src/vm/assemblynamesconfigfactory.h | 72 + src/vm/assemblynative.cpp | 2635 +++ src/vm/assemblynative.hpp | 264 + src/vm/assemblynativeresource.cpp | 585 + src/vm/assemblynativeresource.h | 134 + src/vm/assemblysink.cpp | 153 + src/vm/assemblysink.h | 59 + src/vm/assemblyspec.cpp | 2473 +++ src/vm/assemblyspec.hpp | 739 + src/vm/assemblyspecbase.h | 28 + src/vm/baseassemblyspec.cpp | 749 + src/vm/baseassemblyspec.h | 303 + src/vm/baseassemblyspec.inl | 717 + src/vm/binder.cpp | 1335 ++ src/vm/binder.h | 500 + src/vm/cachelinealloc.cpp | 295 + src/vm/cachelinealloc.h | 146 + src/vm/callhelpers.cpp | 703 + src/vm/callhelpers.h | 681 + src/vm/callingconvention.h | 1710 ++ src/vm/ceeload.cpp | 16168 +++++++++++++++++++ src/vm/ceeload.h | 3741 +++++ src/vm/ceeload.inl | 663 + src/vm/ceemain.cpp | 5063 ++++++ src/vm/ceemain.h | 253 + src/vm/certificatecache.cpp | 85 + src/vm/certificatecache.h | 40 + src/vm/cgensys.h | 180 + src/vm/class.cpp | 3464 ++++ src/vm/class.h | 2681 +++ src/vm/class.inl | 62 + src/vm/classcompat.cpp | 3710 +++++ src/vm/classcompat.h | 826 + src/vm/classfactory.cpp | 999 ++ src/vm/classhash.cpp | 1103 ++ src/vm/classhash.h | 141 + src/vm/classhash.inl | 67 + src/vm/classloadlevel.h | 88 + src/vm/classnames.h | 181 + src/vm/clrex.cpp | 2876 ++++ src/vm/clrex.h | 1316 ++ src/vm/clrex.inl | 51 + src/vm/clrprivbinderappx.cpp | 1057 ++ src/vm/clrprivbinderappx.h | 364 + src/vm/clrprivbinderfusion.cpp | 819 + src/vm/clrprivbinderfusion.h | 228 + src/vm/clrprivbinderloadfile.cpp | 264 + src/vm/clrprivbinderloadfile.h | 148 + src/vm/clrprivbinderreflectiononlywinrt.cpp | 497 + src/vm/clrprivbinderreflectiononlywinrt.h | 295 + src/vm/clrprivbinderutil.cpp | 831 + src/vm/clrprivbinderwinrt.cpp | 1733 ++ src/vm/clrprivbinderwinrt.h | 463 + src/vm/clrprivtypecachereflectiononlywinrt.cpp | 260 + src/vm/clrprivtypecachereflectiononlywinrt.h | 58 + src/vm/clrprivtypecachewinrt.cpp | 246 + src/vm/clrprivtypecachewinrt.h | 70 + src/vm/clrtocomcall.cpp | 1181 ++ src/vm/clrtocomcall.h | 74 + src/vm/clrtracelogging.cpp | 40 + src/vm/clrvarargs.cpp | 122 + src/vm/clrvarargs.h | 27 + src/vm/clsload.cpp | 6789 ++++++++ src/vm/clsload.hpp | 1148 ++ src/vm/clsload.inl | 156 + src/vm/codeman.cpp | 6935 ++++++++ src/vm/codeman.h | 1883 +++ src/vm/codeman.inl | 16 + src/vm/comcache.cpp | 1626 ++ src/vm/comcache.h | 307 + src/vm/comcallablewrapper.cpp | 6769 ++++++++ src/vm/comcallablewrapper.h | 2689 +++ src/vm/comconnectionpoints.cpp | 1308 ++ src/vm/comconnectionpoints.h | 254 + src/vm/comdatetime.cpp | 125 + src/vm/comdatetime.h | 49 + src/vm/comdelegate.cpp | 4284 +++++ src/vm/comdelegate.h | 244 + src/vm/comdependenthandle.cpp | 76 + src/vm/comdependenthandle.h | 51 + src/vm/comdynamic.cpp | 1897 +++ src/vm/comdynamic.h | 201 + src/vm/cominterfacemarshaler.cpp | 1334 ++ src/vm/cominterfacemarshaler.h | 111 + src/vm/comisolatedstorage.cpp | 1073 ++ src/vm/comisolatedstorage.h | 202 + src/vm/commemoryfailpoint.cpp | 43 + src/vm/commemoryfailpoint.h | 28 + src/vm/commethodrental.cpp | 120 + src/vm/commethodrental.h | 29 + src/vm/commodule.cpp | 1289 ++ src/vm/commodule.h | 143 + src/vm/common.cpp | 7 + src/vm/common.h | 537 + src/vm/commtmemberinfomap.cpp | 1582 ++ src/vm/commtmemberinfomap.h | 220 + src/vm/compatibilityswitch.cpp | 107 + src/vm/compatibilityswitch.h | 26 + src/vm/compile.cpp | 8435 ++++++++++ src/vm/compile.h | 914 ++ src/vm/comreflectioncache.hpp | 269 + src/vm/comreflectioncache.inl | 31 + src/vm/comsynchronizable.cpp | 2243 +++ src/vm/comsynchronizable.h | 158 + src/vm/comthreadpool.cpp | 1017 ++ src/vm/comthreadpool.h | 83 + src/vm/comtoclrcall.cpp | 2074 +++ src/vm/comtoclrcall.h | 482 + src/vm/comtypelibconverter.cpp | 791 + src/vm/comtypelibconverter.h | 107 + src/vm/comutilnative.cpp | 3282 ++++ src/vm/comutilnative.h | 326 + src/vm/comwaithandle.cpp | 463 + src/vm/comwaithandle.h | 28 + src/vm/confighelper.cpp | 309 + src/vm/confighelper.h | 203 + src/vm/constrainedexecutionregion.cpp | 2270 +++ src/vm/constrainedexecutionregion.h | 563 + src/vm/context.h | 230 + src/vm/contexts.cpp | 939 ++ src/vm/contractimpl.cpp | 715 + src/vm/contractimpl.h | 1020 ++ src/vm/coreassemblyspec.cpp | 698 + src/vm/corebindresult.cpp | 66 + src/vm/coreclr/.gitmirror | 1 + src/vm/coreclr/corebindresult.h | 62 + src/vm/coreclr/corebindresult.inl | 129 + src/vm/corhost.cpp | 8852 ++++++++++ src/vm/coverage.cpp | 55 + src/vm/coverage.h | 19 + src/vm/crossdomaincalls.cpp | 2587 +++ src/vm/crossdomaincalls.h | 272 + src/vm/crossgen/.gitmirror | 1 + src/vm/crossgen/CMakeLists.txt | 157 + src/vm/crossgen/wks_crossgen.nativeproj | 164 + src/vm/crossgen_mscorlib/.gitmirror | 1 + src/vm/crossgen_mscorlib/CMakeLists.txt | 15 + .../crossgen_mscorlib/mscorlib_crossgen.nativeproj | 27 + src/vm/crossgencompile.cpp | 473 + src/vm/crossgenroparsetypename.cpp | 494 + src/vm/crossgenroresolvenamespace.cpp | 194 + src/vm/crossgenroresolvenamespace.h | 27 + src/vm/crst.cpp | 994 ++ src/vm/crst.h | 546 + src/vm/ctxtcall.h | 410 + src/vm/customattribute.cpp | 1693 ++ src/vm/customattribute.h | 239 + src/vm/custommarshalerinfo.cpp | 641 + src/vm/custommarshalerinfo.h | 320 + src/vm/dac/.gitmirror | 1 + src/vm/dac/CMakeLists.txt | 5 + src/vm/dac/dacwks.targets | 167 + src/vm/dac/dirs.proj | 18 + src/vm/dangerousapis.h | 71 + src/vm/dataimage.cpp | 2534 +++ src/vm/dataimage.h | 451 + src/vm/dataimagesection.h | 104 + src/vm/dbginterface.h | 419 + src/vm/debugdebugger.cpp | 1771 ++ src/vm/debugdebugger.h | 393 + src/vm/debughelp.cpp | 1246 ++ src/vm/debuginfostore.cpp | 711 + src/vm/debuginfostore.h | 129 + src/vm/decodemd.cpp | 517 + src/vm/decodemd.h | 77 + src/vm/delegateinfo.h | 85 + src/vm/dirs.proj | 20 + src/vm/disassembler.cpp | 329 + src/vm/disassembler.h | 115 + src/vm/dispatchinfo.cpp | 3771 +++++ src/vm/dispatchinfo.h | 410 + src/vm/dispparammarshaler.cpp | 645 + src/vm/dispparammarshaler.h | 227 + src/vm/dllimport.cpp | 7724 +++++++++ src/vm/dllimport.h | 791 + src/vm/dllimportcallback.cpp | 1538 ++ src/vm/dllimportcallback.h | 600 + src/vm/domainfile.cpp | 4473 +++++ src/vm/domainfile.h | 946 ++ src/vm/domainfile.inl | 132 + src/vm/dwbucketmanager.hpp | 1493 ++ src/vm/dwreport.cpp | 3336 ++++ src/vm/dwreport.h | 105 + src/vm/dynamicmethod.cpp | 1589 ++ src/vm/dynamicmethod.h | 380 + src/vm/ecall.cpp | 787 + src/vm/ecall.h | 142 + src/vm/ecalllist.h | 2483 +++ src/vm/eeconfig.cpp | 2254 +++ src/vm/eeconfig.h | 1367 ++ src/vm/eeconfigfactory.cpp | 398 + src/vm/eeconfigfactory.h | 149 + src/vm/eecontract.cpp | 272 + src/vm/eecontract.h | 115 + src/vm/eedbginterface.h | 377 + src/vm/eedbginterfaceimpl.cpp | 1683 ++ src/vm/eedbginterfaceimpl.h | 349 + src/vm/eedbginterfaceimpl.inl | 122 + src/vm/eehash.cpp | 536 + src/vm/eehash.h | 609 + src/vm/eehash.inl | 877 + src/vm/eemessagebox.cpp | 181 + src/vm/eemessagebox.h | 70 + src/vm/eepolicy.cpp | 1586 ++ src/vm/eepolicy.h | 191 + src/vm/eeprofinterfaces.h | 66 + src/vm/eeprofinterfaces.inl | 27 + src/vm/eetoprofinterfaceimpl.cpp | 6339 ++++++++ src/vm/eetoprofinterfaceimpl.h | 681 + src/vm/eetoprofinterfaceimpl.inl | 258 + src/vm/eetoprofinterfacewrapper.inl | 242 + src/vm/eetwain.cpp | 5892 +++++++ src/vm/encee.cpp | 1753 ++ src/vm/encee.h | 448 + src/vm/eventreporter.cpp | 837 + src/vm/eventreporter.h | 77 + src/vm/eventstore.cpp | 219 + src/vm/eventstore.hpp | 32 + src/vm/eventtrace.cpp | 7461 +++++++++ src/vm/eventtracepriv.h | 414 + src/vm/excep.cpp | 14119 ++++++++++++++++ src/vm/excep.h | 965 ++ src/vm/exceptionhandling.cpp | 7090 ++++++++ src/vm/exceptionhandling.h | 836 + src/vm/exceptmacros.h | 613 + src/vm/exinfo.cpp | 306 + src/vm/exinfo.h | 183 + src/vm/exstate.cpp | 647 + src/vm/exstate.h | 373 + src/vm/exstatecommon.h | 528 + src/vm/extensibleclassfactory.cpp | 130 + src/vm/extensibleclassfactory.h | 35 + src/vm/fcall.cpp | 412 + src/vm/fcall.h | 1381 ++ src/vm/field.cpp | 1024 ++ src/vm/field.h | 884 + src/vm/fieldmarshaler.cpp | 4870 ++++++ src/vm/fieldmarshaler.h | 1887 +++ src/vm/finalizerthread.cpp | 1496 ++ src/vm/finalizerthread.h | 98 + src/vm/formattype.cpp | 9 + src/vm/fptrstubs.cpp | 167 + src/vm/fptrstubs.h | 83 + src/vm/frames.cpp | 2233 +++ src/vm/frames.h | 3836 +++++ src/vm/frameworkexceptionloader.cpp | 103 + src/vm/frameworkexceptionloader.h | 26 + src/vm/fusionbind.cpp | 661 + src/vm/fusioninit.cpp | 624 + src/vm/fusionsink.cpp | 215 + src/vm/gc.h | 5 + src/vm/gccover.cpp | 1822 +++ src/vm/gccover.h | 111 + src/vm/gcdecode.cpp | 15 + src/vm/gcdesc.h | 5 + src/vm/gcenv.ee.cpp | 841 + src/vm/gcenv.ee.h | 5 + src/vm/gcenv.h | 76 + src/vm/gcenv.interlocked.h | 5 + src/vm/gcenv.interlocked.inl | 5 + src/vm/gcenv.os.cpp | 735 + src/vm/gcenv.os.h | 5 + src/vm/gchelpers.cpp | 1362 ++ src/vm/gchelpers.h | 123 + src/vm/gchost.cpp | 276 + src/vm/gcinfodecoder.cpp | 1952 +++ src/vm/gcscan.h | 5 + src/vm/gcstress.h | 554 + src/vm/gdbjit.cpp | 1185 ++ src/vm/gdbjit.h | 114 + src/vm/gdbjithelpers.h | 31 + src/vm/genericdict.cpp | 1239 ++ src/vm/genericdict.h | 331 + src/vm/generics.cpp | 1145 ++ src/vm/generics.h | 180 + src/vm/generics.inl | 98 + src/vm/genmeth.cpp | 1789 ++ src/vm/gms.h | 6 + src/vm/h2inc.pl | 62 + src/vm/h2inc.ps1 | 70 + src/vm/handletable.h | 5 + src/vm/handletable.inl | 5 + src/vm/hash.cpp | 1234 ++ src/vm/hash.h | 790 + src/vm/hillclimbing.cpp | 439 + src/vm/hillclimbing.h | 97 + src/vm/hostexecutioncontext.cpp | 230 + src/vm/hostexecutioncontext.h | 29 + src/vm/hosting.cpp | 1905 +++ src/vm/hosting.h | 64 + src/vm/i386/.gitmirror | 1 + src/vm/i386/CLRErrorReporting.vrg | 5 + src/vm/i386/RedirectedHandledJITCase.asm | 136 + src/vm/i386/asmconstants.h | 485 + src/vm/i386/asmhelpers.asm | 2400 +++ src/vm/i386/cgencpu.h | 573 + src/vm/i386/cgenx86.cpp | 2257 +++ src/vm/i386/excepcpu.h | 87 + src/vm/i386/excepx86.cpp | 3734 +++++ src/vm/i386/fptext.asm | 277 + src/vm/i386/gmsasm.asm | 37 + src/vm/i386/gmscpu.h | 140 + src/vm/i386/gmsx86.cpp | 1245 ++ src/vm/i386/jithelp.asm | 2574 +++ src/vm/i386/jitinterfacex86.cpp | 1922 +++ src/vm/i386/profiler.cpp | 336 + src/vm/i386/remotingx86.cpp | 225 + src/vm/i386/stublinkerx86.cpp | 6806 ++++++++ src/vm/i386/stublinkerx86.h | 781 + src/vm/i386/virtualcallstubcpu.hpp | 1077 ++ src/vm/ibclogger.cpp | 1197 ++ src/vm/ibclogger.h | 621 + src/vm/ildump.h | 222 + src/vm/ilmarshalers.cpp | 6379 ++++++++ src/vm/ilmarshalers.h | 3443 ++++ src/vm/ilstubcache.cpp | 975 ++ src/vm/ilstubcache.h | 237 + src/vm/ilstubresolver.cpp | 521 + src/vm/ilstubresolver.h | 125 + src/vm/inlinetracking.cpp | 429 + src/vm/inlinetracking.h | 232 + src/vm/instmethhash.cpp | 440 + src/vm/instmethhash.h | 172 + src/vm/interopconverter.cpp | 984 ++ src/vm/interopconverter.h | 183 + src/vm/interoputil.cpp | 7221 +++++++++ src/vm/interoputil.h | 528 + src/vm/interoputil.inl | 79 + src/vm/interpreter.cpp | 12253 ++++++++++++++ src/vm/interpreter.h | 2051 +++ src/vm/interpreter.hpp | 481 + src/vm/invalidoverlappedwrappers.h | 70 + src/vm/invokeutil.cpp | 2129 +++ src/vm/invokeutil.h | 335 + src/vm/iterator_util.h | 333 + src/vm/jithelpers.cpp | 7199 +++++++++ src/vm/jitinterface.cpp | 14078 ++++++++++++++++ src/vm/jitinterface.h | 1684 ++ src/vm/jitinterfacegen.cpp | 301 + src/vm/jupiterobject.h | 91 + src/vm/listlock.cpp | 96 + src/vm/listlock.h | 357 + src/vm/listlock.inl | 51 + src/vm/loaderallocator.cpp | 1670 ++ src/vm/loaderallocator.hpp | 523 + src/vm/loaderallocator.inl | 183 + src/vm/managedmdimport.cpp | 722 + src/vm/managedmdimport.hpp | 122 + src/vm/marshalnative.cpp | 2707 ++++ src/vm/marshalnative.h | 243 + src/vm/marvin32.cpp | 266 + src/vm/mda.cpp | 4017 +++++ src/vm/mda.h | 1514 ++ src/vm/mda.inl | 14 + src/vm/mdaBoilerplate.exe.mda.config | 1134 ++ src/vm/mdaassistants.cpp | 2352 +++ src/vm/mdaassistants.h | 932 ++ src/vm/mdaassistantschemas.inl | 639 + src/vm/mdadac.cpp | 48 + src/vm/mdagroups.inl | 73 + src/vm/mdamacroscrubber.inl | 295 + src/vm/mdaschema.inl | 575 + src/vm/memberload.cpp | 1566 ++ src/vm/memberload.h | 265 + src/vm/message.cpp | 1171 ++ src/vm/message.h | 200 + src/vm/metasig.h | 731 + src/vm/method.cpp | 5879 +++++++ src/vm/method.hpp | 3601 +++++ src/vm/method.inl | 215 + src/vm/methodimpl.cpp | 285 + src/vm/methodimpl.h | 128 + src/vm/methoditer.cpp | 370 + src/vm/methoditer.h | 127 + src/vm/methodtable.cpp | 10017 ++++++++++++ src/vm/methodtable.h | 4416 +++++ src/vm/methodtable.inl | 1927 +++ src/vm/methodtablebuilder.cpp | 13413 +++++++++++++++ src/vm/methodtablebuilder.h | 3048 ++++ src/vm/methodtablebuilder.inl | 523 + src/vm/microsoft.comservices.h | 277 + src/vm/microsoft.comservices_i.c | 175 + src/vm/mixedmode.cpp | 236 + src/vm/mixedmode.hpp | 139 + src/vm/mlinfo.cpp | 5395 +++++++ src/vm/mlinfo.h | 998 ++ src/vm/mngstdinterfaces.cpp | 1030 ++ src/vm/mngstdinterfaces.h | 398 + src/vm/mngstditflist.h | 140 + src/vm/mscorlib.cpp | 489 + src/vm/mscorlib.h | 2225 +++ src/vm/mscoruefwrapper.h | 19 + src/vm/mtypes.h | 122 + src/vm/multicorejit.cpp | 1662 ++ src/vm/multicorejit.h | 277 + src/vm/multicorejitimpl.h | 497 + src/vm/multicorejitplayer.cpp | 1486 ++ src/vm/namespace.h | 83 + src/vm/nativeeventsource.cpp | 38 + src/vm/nativeeventsource.h | 26 + src/vm/nativeformatreader.h | 526 + src/vm/nativeoverlapped.cpp | 534 + src/vm/nativeoverlapped.h | 156 + src/vm/newcompressedstack.cpp | 1074 ++ src/vm/newcompressedstack.h | 197 + src/vm/ngenhash.h | 484 + src/vm/ngenhash.inl | 1522 ++ src/vm/ngenoptout.cpp | 37 + src/vm/ngenoptout.h | 34 + src/vm/notifyexternals.cpp | 61 + src/vm/notifyexternals.h | 20 + src/vm/nsenumhandleallcases.h | 43 + src/vm/nsenums.h | 76 + src/vm/object.cpp | 3663 +++++ src/vm/object.h | 4675 ++++++ src/vm/object.inl | 301 + src/vm/objectclone.cpp | 3865 +++++ src/vm/objectclone.h | 1268 ++ src/vm/objecthandle.h | 5 + src/vm/objectlist.cpp | 209 + src/vm/objectlist.h | 100 + src/vm/olecontexthelpers.cpp | 171 + src/vm/olecontexthelpers.h | 29 + src/vm/oletls.h | 210 + src/vm/olevariant.cpp | 5277 ++++++ src/vm/olevariant.h | 622 + src/vm/packedfields.inl | 345 + src/vm/pefile.cpp | 5163 ++++++ src/vm/pefile.h | 1243 ++ src/vm/pefile.inl | 2165 +++ src/vm/pefingerprint.cpp | 623 + src/vm/pefingerprint.h | 126 + src/vm/peimage.cpp | 2160 +++ src/vm/peimage.h | 501 + src/vm/peimage.inl | 919 ++ src/vm/peimagelayout.cpp | 873 + src/vm/peimagelayout.h | 196 + src/vm/peimagelayout.inl | 121 + src/vm/pendingload.cpp | 254 + src/vm/pendingload.h | 265 + src/vm/perfdefaults.cpp | 147 + src/vm/perfdefaults.h | 90 + src/vm/perfinfo.cpp | 126 + src/vm/perfinfo.h | 40 + src/vm/perfmap.cpp | 324 + src/vm/perfmap.h | 86 + src/vm/precode.cpp | 792 + src/vm/precode.h | 369 + src/vm/prestub.cpp | 2877 ++++ src/vm/profattach.cpp | 1336 ++ src/vm/profattach.h | 425 + src/vm/profattach.inl | 566 + src/vm/profattachclient.cpp | 948 ++ src/vm/profattachclient.h | 78 + src/vm/profattachserver.cpp | 1296 ++ src/vm/profattachserver.h | 109 + src/vm/profattachserver.inl | 129 + src/vm/profdetach.cpp | 713 + src/vm/profdetach.h | 78 + src/vm/profilermetadataemitvalidator.cpp | 1792 ++ src/vm/profilermetadataemitvalidator.h | 1044 ++ src/vm/profilingenumerators.cpp | 692 + src/vm/profilingenumerators.h | 533 + src/vm/profilinghelper.cpp | 1493 ++ src/vm/profilinghelper.h | 130 + src/vm/profilinghelper.inl | 276 + src/vm/proftoeeinterfaceimpl.cpp | 10041 ++++++++++++ src/vm/proftoeeinterfaceimpl.h | 638 + src/vm/proftoeeinterfaceimpl.inl | 195 + src/vm/qcall.cpp | 107 + src/vm/qcall.h | 357 + src/vm/rcwrefcache.cpp | 290 + src/vm/rcwrefcache.h | 103 + src/vm/rcwwalker.cpp | 983 ++ src/vm/rcwwalker.h | 152 + src/vm/readytoruninfo.cpp | 780 + src/vm/readytoruninfo.h | 144 + src/vm/reflectclasswriter.cpp | 247 + src/vm/reflectclasswriter.h | 103 + src/vm/reflectioninvocation.cpp | 3890 +++++ src/vm/reflectioninvocation.h | 124 + src/vm/registration.h | 25 + src/vm/rejit.cpp | 4015 +++++ src/vm/rejit.h | 569 + src/vm/rejit.inl | 345 + src/vm/remoting.cpp | 3773 +++++ src/vm/remoting.h | 957 ++ src/vm/rexcep.h | 355 + src/vm/rtlfunctions.cpp | 132 + src/vm/rtlfunctions.h | 79 + src/vm/runtimecallablewrapper.cpp | 5615 +++++++ src/vm/runtimecallablewrapper.h | 2233 +++ src/vm/runtimeexceptionkind.h | 31 + src/vm/runtimehandles.cpp | 3569 ++++ src/vm/runtimehandles.h | 675 + src/vm/rwlock.cpp | 2952 ++++ src/vm/rwlock.h | 287 + src/vm/safehandle.cpp | 506 + src/vm/security.cpp | 102 + src/vm/security.h | 380 + src/vm/security.inl | 783 + src/vm/securityattributes.cpp | 2764 ++++ src/vm/securityattributes.h | 146 + src/vm/securityattributes.inl | 44 + src/vm/securityconfig.cpp | 2181 +++ src/vm/securityconfig.h | 122 + src/vm/securitydeclarative.cpp | 1789 ++ src/vm/securitydeclarative.h | 198 + src/vm/securitydeclarative.inl | 134 + src/vm/securitydeclarativecache.cpp | 357 + src/vm/securitydeclarativecache.h | 138 + src/vm/securitydescriptor.cpp | 478 + src/vm/securitydescriptor.h | 201 + src/vm/securitydescriptor.inl | 109 + src/vm/securitydescriptorappdomain.cpp | 786 + src/vm/securitydescriptorappdomain.h | 186 + src/vm/securitydescriptorappdomain.inl | 127 + src/vm/securitydescriptorassembly.cpp | 1007 ++ src/vm/securitydescriptorassembly.h | 196 + src/vm/securitydescriptorassembly.inl | 94 + src/vm/securityhostprotection.cpp | 102 + src/vm/securityhostprotection.h | 14 + src/vm/securityimperative.cpp | 119 + src/vm/securityimperative.h | 36 + src/vm/securitymeta.cpp | 2318 +++ src/vm/securitymeta.h | 668 + src/vm/securitymeta.inl | 1272 ++ src/vm/securitypolicy.cpp | 1283 ++ src/vm/securitypolicy.h | 349 + src/vm/securityprincipal.cpp | 227 + src/vm/securityprincipal.h | 29 + src/vm/securitystackwalk.cpp | 2439 +++ src/vm/securitystackwalk.h | 294 + src/vm/securitytransparentassembly.cpp | 1819 +++ src/vm/securitytransparentassembly.h | 249 + src/vm/securitytransparentassembly.inl | 259 + src/vm/sha1.cpp | 412 + src/vm/sha1.h | 54 + src/vm/sigformat.cpp | 653 + src/vm/sigformat.h | 43 + src/vm/siginfo.cpp | 5630 +++++++ src/vm/siginfo.hpp | 1194 ++ src/vm/simplerwlock.cpp | 307 + src/vm/simplerwlock.hpp | 250 + src/vm/sourceline.cpp | 334 + src/vm/sourceline.h | 44 + src/vm/specialstatics.h | 40 + src/vm/spinlock.cpp | 512 + src/vm/spinlock.h | 306 + src/vm/stackbuildersink.cpp | 702 + src/vm/stackbuildersink.h | 49 + src/vm/stackcompressor.cpp | 378 + src/vm/stackcompressor.h | 39 + src/vm/stackingallocator.cpp | 375 + src/vm/stackingallocator.h | 315 + src/vm/stackprobe.cpp | 1793 ++ src/vm/stackprobe.h | 1007 ++ src/vm/stackprobe.inl | 135 + src/vm/stacksampler.cpp | 467 + src/vm/stacksampler.h | 81 + src/vm/stackwalk.cpp | 3493 ++++ src/vm/stackwalk.h | 715 + src/vm/stackwalktypes.h | 246 + src/vm/staticallocationhelpers.inl | 160 + src/vm/stdinterfaces.cpp | 3729 +++++ src/vm/stdinterfaces.h | 561 + src/vm/stdinterfaces_internal.h | 376 + src/vm/stdinterfaces_wrapper.cpp | 3269 ++++ src/vm/stringliteralmap.cpp | 666 + src/vm/stringliteralmap.h | 294 + src/vm/stubcache.cpp | 302 + src/vm/stubcache.h | 139 + src/vm/stubgen.cpp | 2907 ++++ src/vm/stubgen.h | 738 + src/vm/stubhelpers.cpp | 2176 +++ src/vm/stubhelpers.h | 167 + src/vm/stublink.cpp | 2597 +++ src/vm/stublink.h | 1233 ++ src/vm/stublink.inl | 116 + src/vm/stubmgr.cpp | 2680 +++ src/vm/stubmgr.h | 998 ++ src/vm/syncblk.cpp | 3657 +++++ src/vm/syncblk.h | 1390 ++ src/vm/syncblk.inl | 291 + src/vm/syncclean.cpp | 103 + src/vm/syncclean.hpp | 30 + src/vm/synch.cpp | 1157 ++ src/vm/synch.h | 216 + src/vm/synchronizationcontextnative.cpp | 160 + src/vm/synchronizationcontextnative.h | 32 + src/vm/testhookmgr.cpp | 778 + src/vm/testhookmgr.h | 101 + src/vm/threaddebugblockinginfo.cpp | 90 + src/vm/threaddebugblockinginfo.h | 80 + src/vm/threadpoolrequest.cpp | 800 + src/vm/threadpoolrequest.h | 362 + src/vm/threads.cpp | 13674 ++++++++++++++++ src/vm/threads.h | 7880 +++++++++ src/vm/threads.inl | 318 + src/vm/threadstatics.cpp | 709 + src/vm/threadstatics.h | 674 + src/vm/threadsuspend.cpp | 8623 ++++++++++ src/vm/threadsuspend.h | 267 + src/vm/tlbexport.cpp | 6344 ++++++++ src/vm/tlbexport.h | 485 + src/vm/typectxt.cpp | 335 + src/vm/typectxt.h | 191 + src/vm/typedesc.cpp | 2458 +++ src/vm/typedesc.h | 686 + src/vm/typedesc.inl | 70 + src/vm/typeequivalencehash.cpp | 100 + src/vm/typeequivalencehash.hpp | 116 + src/vm/typehandle.cpp | 2085 +++ src/vm/typehandle.h | 833 + src/vm/typehandle.inl | 285 + src/vm/typehash.cpp | 873 + src/vm/typehash.h | 160 + src/vm/typehashingalgorithms.h | 99 + src/vm/typekey.h | 307 + src/vm/typeparse.cpp | 1939 +++ src/vm/typeparse.h | 469 + src/vm/typestring.cpp | 1678 ++ src/vm/typestring.h | 266 + src/vm/umthunkhash.cpp | 171 + src/vm/umthunkhash.h | 87 + src/vm/util.cpp | 4155 +++++ src/vm/util.hpp | 1382 ++ src/vm/validator.cpp | 946 ++ src/vm/vars.cpp | 371 + src/vm/vars.hpp | 926 ++ src/vm/verifier.cpp | 469 + src/vm/verifier.hpp | 111 + src/vm/veropcodes.hpp | 30 + src/vm/versionresilienthashcode.cpp | 147 + src/vm/versionresilienthashcode.h | 9 + src/vm/vertable.h | 380 + src/vm/virtualcallstub.cpp | 4256 +++++ src/vm/virtualcallstub.h | 1625 ++ src/vm/vm.settings | 72 + src/vm/vm.targets | 59 + src/vm/vmholder.h | 26 + src/vm/weakreferencenative.cpp | 981 ++ src/vm/weakreferencenative.h | 42 + src/vm/win32threadpool.cpp | 5637 +++++++ src/vm/win32threadpool.h | 1400 ++ src/vm/winrthelpers.cpp | 164 + src/vm/winrthelpers.h | 30 + src/vm/winrtredirector.h | 143 + src/vm/winrtredirector.inl | 70 + src/vm/winrttypenameconverter.cpp | 1051 ++ src/vm/winrttypenameconverter.h | 125 + src/vm/wks/.gitmirror | 1 + src/vm/wks/CMakeLists.txt | 115 + src/vm/wks/wks.nativeproj | 22 + src/vm/wks/wks.targets | 390 + src/vm/wrappers.h | 350 + src/vm/zapsig.cpp | 1521 ++ src/vm/zapsig.h | 213 + 783 files changed, 809087 insertions(+) create mode 100644 src/vm/.gitmirror create mode 100644 src/vm/CMakeLists.txt create mode 100644 src/vm/ClrEtwAll.man create mode 100644 src/vm/ClrEtwAllMeta.lst create mode 100644 src/vm/amd64/.gitmirror create mode 100644 src/vm/amd64/AsmHelpers.asm create mode 100644 src/vm/amd64/AsmMacros.inc create mode 100644 src/vm/amd64/CLRErrorReporting.vrg create mode 100644 src/vm/amd64/CallDescrWorkerAMD64.asm create mode 100644 src/vm/amd64/ComCallPreStub.asm create mode 100644 src/vm/amd64/CrtHelpers.asm create mode 100644 src/vm/amd64/ExternalMethodFixupThunk.asm create mode 100644 src/vm/amd64/GenericComCallStubs.asm create mode 100644 src/vm/amd64/GenericComPlusCallStubs.asm create mode 100644 src/vm/amd64/InstantiatingStub.asm create mode 100644 src/vm/amd64/JitHelpers_Fast.asm create mode 100644 src/vm/amd64/JitHelpers_FastWriteBarriers.asm create mode 100644 src/vm/amd64/JitHelpers_InlineGetAppDomain.asm create mode 100644 src/vm/amd64/JitHelpers_InlineGetThread.asm create mode 100644 src/vm/amd64/JitHelpers_Slow.asm create mode 100644 src/vm/amd64/PInvokeStubs.asm create mode 100644 src/vm/amd64/RedirectedHandledJITCase.asm create mode 100644 src/vm/amd64/RemotingThunksAMD64.asm create mode 100644 src/vm/amd64/ThePreStubAMD64.asm create mode 100644 src/vm/amd64/TlsGetters.asm create mode 100644 src/vm/amd64/UMThunkStub.asm create mode 100644 src/vm/amd64/VirtualCallStubAMD64.asm create mode 100644 src/vm/amd64/asmconstants.h create mode 100644 src/vm/amd64/calldescrworkeramd64.S create mode 100644 src/vm/amd64/cgenamd64.cpp create mode 100644 src/vm/amd64/cgencpu.h create mode 100644 src/vm/amd64/crthelpers.S create mode 100644 src/vm/amd64/excepamd64.cpp create mode 100644 src/vm/amd64/excepcpu.h create mode 100644 src/vm/amd64/externalmethodfixupthunk.S create mode 100644 src/vm/amd64/getstate.S create mode 100644 src/vm/amd64/getstate.asm create mode 100644 src/vm/amd64/gmsamd64.cpp create mode 100644 src/vm/amd64/gmscpu.h create mode 100644 src/vm/amd64/jithelpers_fast.S create mode 100644 src/vm/amd64/jithelpers_fastwritebarriers.S create mode 100644 src/vm/amd64/jithelpers_slow.S create mode 100644 src/vm/amd64/jithelpersamd64.cpp create mode 100644 src/vm/amd64/jitinterfaceamd64.cpp create mode 100644 src/vm/amd64/pinvokestubs.S create mode 100644 src/vm/amd64/profiler.cpp create mode 100644 src/vm/amd64/remotingamd64.cpp create mode 100644 src/vm/amd64/stublinkeramd64.cpp create mode 100644 src/vm/amd64/stublinkeramd64.h create mode 100644 src/vm/amd64/theprestubamd64.S create mode 100644 src/vm/amd64/umthunkstub.S create mode 100644 src/vm/amd64/unixasmhelpers.S create mode 100644 src/vm/amd64/unixstubs.cpp create mode 100644 src/vm/amd64/virtualcallstubamd64.S create mode 100644 src/vm/amd64/virtualcallstubcpu.hpp create mode 100644 src/vm/appdomain.cpp create mode 100644 src/vm/appdomain.hpp create mode 100644 src/vm/appdomain.inl create mode 100644 src/vm/appdomainconfigfactory.hpp create mode 100644 src/vm/appdomainhelper.cpp create mode 100644 src/vm/appdomainhelper.h create mode 100644 src/vm/appdomainnative.cpp create mode 100644 src/vm/appdomainnative.hpp create mode 100644 src/vm/appdomainstack.cpp create mode 100644 src/vm/appdomainstack.h create mode 100644 src/vm/appdomainstack.inl create mode 100644 src/vm/appxutil.cpp create mode 100644 src/vm/appxutil.h create mode 100644 src/vm/aptca.cpp create mode 100644 src/vm/aptca.h create mode 100644 src/vm/argdestination.h create mode 100644 src/vm/argslot.h create mode 100644 src/vm/arm/.gitmirror create mode 100644 src/vm/arm/CrtHelpers.asm create mode 100644 src/vm/arm/PInvokeStubs.asm create mode 100644 src/vm/arm/armsinglestepper.cpp create mode 100644 src/vm/arm/asmconstants.h create mode 100644 src/vm/arm/asmhelpers.S create mode 100644 src/vm/arm/asmhelpers.asm create mode 100644 src/vm/arm/asmmacros.h create mode 100644 src/vm/arm/cgencpu.h create mode 100644 src/vm/arm/crthelpers.S create mode 100644 src/vm/arm/ehhelpers.S create mode 100644 src/vm/arm/ehhelpers.asm create mode 100644 src/vm/arm/exceparm.cpp create mode 100644 src/vm/arm/excepcpu.h create mode 100644 src/vm/arm/gmscpu.h create mode 100644 src/vm/arm/jithelpersarm.cpp create mode 100644 src/vm/arm/memcpy.S create mode 100644 src/vm/arm/memcpy.asm create mode 100644 src/vm/arm/memcpy_crt.asm create mode 100644 src/vm/arm/patchedcode.S create mode 100644 src/vm/arm/patchedcode.asm create mode 100644 src/vm/arm/pinvokestubs.S create mode 100644 src/vm/arm/profiler.cpp create mode 100644 src/vm/arm/stubs.cpp create mode 100644 src/vm/arm/unixstubs.cpp create mode 100644 src/vm/arm/virtualcallstubcpu.hpp create mode 100644 src/vm/arm64/.gitmirror create mode 100644 src/vm/arm64/CallDescrWorkerARM64.asm create mode 100644 src/vm/arm64/PInvokeStubs.asm create mode 100644 src/vm/arm64/asmconstants.h create mode 100644 src/vm/arm64/asmhelpers.S create mode 100644 src/vm/arm64/asmhelpers.asm create mode 100644 src/vm/arm64/asmmacros.h create mode 100644 src/vm/arm64/calldescrworkerarm64.S create mode 100644 src/vm/arm64/cgenarm64.cpp create mode 100644 src/vm/arm64/cgencpu.h create mode 100644 src/vm/arm64/crthelpers.S create mode 100644 src/vm/arm64/crthelpers.asm create mode 100644 src/vm/arm64/excepcpu.h create mode 100644 src/vm/arm64/gmscpu.h create mode 100644 src/vm/arm64/pinvokestubs.S create mode 100644 src/vm/arm64/stubs.cpp create mode 100644 src/vm/arm64/unixstubs.cpp create mode 100644 src/vm/arm64/virtualcallstubcpu.hpp create mode 100644 src/vm/armsinglestepper.h create mode 100644 src/vm/array.cpp create mode 100644 src/vm/array.h create mode 100644 src/vm/assembly.cpp create mode 100644 src/vm/assembly.hpp create mode 100644 src/vm/assemblyname.cpp create mode 100644 src/vm/assemblyname.hpp create mode 100644 src/vm/assemblynamelist.h create mode 100644 src/vm/assemblynamesconfigfactory.cpp create mode 100644 src/vm/assemblynamesconfigfactory.h create mode 100644 src/vm/assemblynative.cpp create mode 100644 src/vm/assemblynative.hpp create mode 100644 src/vm/assemblynativeresource.cpp create mode 100644 src/vm/assemblynativeresource.h create mode 100644 src/vm/assemblysink.cpp create mode 100644 src/vm/assemblysink.h create mode 100644 src/vm/assemblyspec.cpp create mode 100644 src/vm/assemblyspec.hpp create mode 100644 src/vm/assemblyspecbase.h create mode 100644 src/vm/baseassemblyspec.cpp create mode 100644 src/vm/baseassemblyspec.h create mode 100644 src/vm/baseassemblyspec.inl create mode 100644 src/vm/binder.cpp create mode 100644 src/vm/binder.h create mode 100644 src/vm/cachelinealloc.cpp create mode 100644 src/vm/cachelinealloc.h create mode 100644 src/vm/callhelpers.cpp create mode 100644 src/vm/callhelpers.h create mode 100644 src/vm/callingconvention.h create mode 100644 src/vm/ceeload.cpp create mode 100644 src/vm/ceeload.h create mode 100644 src/vm/ceeload.inl create mode 100644 src/vm/ceemain.cpp create mode 100644 src/vm/ceemain.h create mode 100644 src/vm/certificatecache.cpp create mode 100644 src/vm/certificatecache.h create mode 100644 src/vm/cgensys.h create mode 100644 src/vm/class.cpp create mode 100644 src/vm/class.h create mode 100644 src/vm/class.inl create mode 100644 src/vm/classcompat.cpp create mode 100644 src/vm/classcompat.h create mode 100644 src/vm/classfactory.cpp create mode 100644 src/vm/classhash.cpp create mode 100644 src/vm/classhash.h create mode 100644 src/vm/classhash.inl create mode 100644 src/vm/classloadlevel.h create mode 100644 src/vm/classnames.h create mode 100644 src/vm/clrex.cpp create mode 100644 src/vm/clrex.h create mode 100644 src/vm/clrex.inl create mode 100644 src/vm/clrprivbinderappx.cpp create mode 100644 src/vm/clrprivbinderappx.h create mode 100644 src/vm/clrprivbinderfusion.cpp create mode 100644 src/vm/clrprivbinderfusion.h create mode 100644 src/vm/clrprivbinderloadfile.cpp create mode 100644 src/vm/clrprivbinderloadfile.h create mode 100644 src/vm/clrprivbinderreflectiononlywinrt.cpp create mode 100644 src/vm/clrprivbinderreflectiononlywinrt.h create mode 100644 src/vm/clrprivbinderutil.cpp create mode 100644 src/vm/clrprivbinderwinrt.cpp create mode 100644 src/vm/clrprivbinderwinrt.h create mode 100644 src/vm/clrprivtypecachereflectiononlywinrt.cpp create mode 100644 src/vm/clrprivtypecachereflectiononlywinrt.h create mode 100644 src/vm/clrprivtypecachewinrt.cpp create mode 100644 src/vm/clrprivtypecachewinrt.h create mode 100644 src/vm/clrtocomcall.cpp create mode 100644 src/vm/clrtocomcall.h create mode 100644 src/vm/clrtracelogging.cpp create mode 100644 src/vm/clrvarargs.cpp create mode 100644 src/vm/clrvarargs.h create mode 100644 src/vm/clsload.cpp create mode 100644 src/vm/clsload.hpp create mode 100644 src/vm/clsload.inl create mode 100644 src/vm/codeman.cpp create mode 100644 src/vm/codeman.h create mode 100644 src/vm/codeman.inl create mode 100644 src/vm/comcache.cpp create mode 100644 src/vm/comcache.h create mode 100644 src/vm/comcallablewrapper.cpp create mode 100644 src/vm/comcallablewrapper.h create mode 100644 src/vm/comconnectionpoints.cpp create mode 100644 src/vm/comconnectionpoints.h create mode 100644 src/vm/comdatetime.cpp create mode 100644 src/vm/comdatetime.h create mode 100644 src/vm/comdelegate.cpp create mode 100644 src/vm/comdelegate.h create mode 100644 src/vm/comdependenthandle.cpp create mode 100644 src/vm/comdependenthandle.h create mode 100644 src/vm/comdynamic.cpp create mode 100644 src/vm/comdynamic.h create mode 100644 src/vm/cominterfacemarshaler.cpp create mode 100644 src/vm/cominterfacemarshaler.h create mode 100644 src/vm/comisolatedstorage.cpp create mode 100644 src/vm/comisolatedstorage.h create mode 100644 src/vm/commemoryfailpoint.cpp create mode 100644 src/vm/commemoryfailpoint.h create mode 100644 src/vm/commethodrental.cpp create mode 100644 src/vm/commethodrental.h create mode 100644 src/vm/commodule.cpp create mode 100644 src/vm/commodule.h create mode 100644 src/vm/common.cpp create mode 100644 src/vm/common.h create mode 100644 src/vm/commtmemberinfomap.cpp create mode 100644 src/vm/commtmemberinfomap.h create mode 100644 src/vm/compatibilityswitch.cpp create mode 100644 src/vm/compatibilityswitch.h create mode 100644 src/vm/compile.cpp create mode 100644 src/vm/compile.h create mode 100644 src/vm/comreflectioncache.hpp create mode 100644 src/vm/comreflectioncache.inl create mode 100644 src/vm/comsynchronizable.cpp create mode 100644 src/vm/comsynchronizable.h create mode 100644 src/vm/comthreadpool.cpp create mode 100644 src/vm/comthreadpool.h create mode 100644 src/vm/comtoclrcall.cpp create mode 100644 src/vm/comtoclrcall.h create mode 100644 src/vm/comtypelibconverter.cpp create mode 100644 src/vm/comtypelibconverter.h create mode 100644 src/vm/comutilnative.cpp create mode 100644 src/vm/comutilnative.h create mode 100644 src/vm/comwaithandle.cpp create mode 100644 src/vm/comwaithandle.h create mode 100644 src/vm/confighelper.cpp create mode 100644 src/vm/confighelper.h create mode 100644 src/vm/constrainedexecutionregion.cpp create mode 100644 src/vm/constrainedexecutionregion.h create mode 100644 src/vm/context.h create mode 100644 src/vm/contexts.cpp create mode 100644 src/vm/contractimpl.cpp create mode 100644 src/vm/contractimpl.h create mode 100644 src/vm/coreassemblyspec.cpp create mode 100644 src/vm/corebindresult.cpp create mode 100644 src/vm/coreclr/.gitmirror create mode 100644 src/vm/coreclr/corebindresult.h create mode 100644 src/vm/coreclr/corebindresult.inl create mode 100644 src/vm/corhost.cpp create mode 100644 src/vm/coverage.cpp create mode 100644 src/vm/coverage.h create mode 100644 src/vm/crossdomaincalls.cpp create mode 100644 src/vm/crossdomaincalls.h create mode 100644 src/vm/crossgen/.gitmirror create mode 100644 src/vm/crossgen/CMakeLists.txt create mode 100644 src/vm/crossgen/wks_crossgen.nativeproj create mode 100644 src/vm/crossgen_mscorlib/.gitmirror create mode 100644 src/vm/crossgen_mscorlib/CMakeLists.txt create mode 100644 src/vm/crossgen_mscorlib/mscorlib_crossgen.nativeproj create mode 100644 src/vm/crossgencompile.cpp create mode 100644 src/vm/crossgenroparsetypename.cpp create mode 100644 src/vm/crossgenroresolvenamespace.cpp create mode 100644 src/vm/crossgenroresolvenamespace.h create mode 100644 src/vm/crst.cpp create mode 100644 src/vm/crst.h create mode 100644 src/vm/ctxtcall.h create mode 100644 src/vm/customattribute.cpp create mode 100644 src/vm/customattribute.h create mode 100644 src/vm/custommarshalerinfo.cpp create mode 100644 src/vm/custommarshalerinfo.h create mode 100644 src/vm/dac/.gitmirror create mode 100644 src/vm/dac/CMakeLists.txt create mode 100644 src/vm/dac/dacwks.targets create mode 100644 src/vm/dac/dirs.proj create mode 100644 src/vm/dangerousapis.h create mode 100644 src/vm/dataimage.cpp create mode 100644 src/vm/dataimage.h create mode 100644 src/vm/dataimagesection.h create mode 100644 src/vm/dbginterface.h create mode 100644 src/vm/debugdebugger.cpp create mode 100644 src/vm/debugdebugger.h create mode 100644 src/vm/debughelp.cpp create mode 100644 src/vm/debuginfostore.cpp create mode 100644 src/vm/debuginfostore.h create mode 100644 src/vm/decodemd.cpp create mode 100644 src/vm/decodemd.h create mode 100644 src/vm/delegateinfo.h create mode 100644 src/vm/dirs.proj create mode 100755 src/vm/disassembler.cpp create mode 100644 src/vm/disassembler.h create mode 100644 src/vm/dispatchinfo.cpp create mode 100644 src/vm/dispatchinfo.h create mode 100644 src/vm/dispparammarshaler.cpp create mode 100644 src/vm/dispparammarshaler.h create mode 100644 src/vm/dllimport.cpp create mode 100644 src/vm/dllimport.h create mode 100644 src/vm/dllimportcallback.cpp create mode 100644 src/vm/dllimportcallback.h create mode 100644 src/vm/domainfile.cpp create mode 100644 src/vm/domainfile.h create mode 100644 src/vm/domainfile.inl create mode 100644 src/vm/dwbucketmanager.hpp create mode 100644 src/vm/dwreport.cpp create mode 100644 src/vm/dwreport.h create mode 100644 src/vm/dynamicmethod.cpp create mode 100644 src/vm/dynamicmethod.h create mode 100644 src/vm/ecall.cpp create mode 100644 src/vm/ecall.h create mode 100644 src/vm/ecalllist.h create mode 100644 src/vm/eeconfig.cpp create mode 100644 src/vm/eeconfig.h create mode 100644 src/vm/eeconfigfactory.cpp create mode 100644 src/vm/eeconfigfactory.h create mode 100644 src/vm/eecontract.cpp create mode 100644 src/vm/eecontract.h create mode 100644 src/vm/eedbginterface.h create mode 100644 src/vm/eedbginterfaceimpl.cpp create mode 100644 src/vm/eedbginterfaceimpl.h create mode 100644 src/vm/eedbginterfaceimpl.inl create mode 100644 src/vm/eehash.cpp create mode 100644 src/vm/eehash.h create mode 100644 src/vm/eehash.inl create mode 100644 src/vm/eemessagebox.cpp create mode 100644 src/vm/eemessagebox.h create mode 100644 src/vm/eepolicy.cpp create mode 100644 src/vm/eepolicy.h create mode 100644 src/vm/eeprofinterfaces.h create mode 100644 src/vm/eeprofinterfaces.inl create mode 100644 src/vm/eetoprofinterfaceimpl.cpp create mode 100644 src/vm/eetoprofinterfaceimpl.h create mode 100644 src/vm/eetoprofinterfaceimpl.inl create mode 100644 src/vm/eetoprofinterfacewrapper.inl create mode 100644 src/vm/eetwain.cpp create mode 100644 src/vm/encee.cpp create mode 100644 src/vm/encee.h create mode 100644 src/vm/eventreporter.cpp create mode 100644 src/vm/eventreporter.h create mode 100644 src/vm/eventstore.cpp create mode 100644 src/vm/eventstore.hpp create mode 100644 src/vm/eventtrace.cpp create mode 100644 src/vm/eventtracepriv.h create mode 100644 src/vm/excep.cpp create mode 100644 src/vm/excep.h create mode 100644 src/vm/exceptionhandling.cpp create mode 100644 src/vm/exceptionhandling.h create mode 100644 src/vm/exceptmacros.h create mode 100644 src/vm/exinfo.cpp create mode 100644 src/vm/exinfo.h create mode 100644 src/vm/exstate.cpp create mode 100644 src/vm/exstate.h create mode 100644 src/vm/exstatecommon.h create mode 100644 src/vm/extensibleclassfactory.cpp create mode 100644 src/vm/extensibleclassfactory.h create mode 100644 src/vm/fcall.cpp create mode 100644 src/vm/fcall.h create mode 100644 src/vm/field.cpp create mode 100644 src/vm/field.h create mode 100644 src/vm/fieldmarshaler.cpp create mode 100644 src/vm/fieldmarshaler.h create mode 100644 src/vm/finalizerthread.cpp create mode 100644 src/vm/finalizerthread.h create mode 100644 src/vm/formattype.cpp create mode 100644 src/vm/fptrstubs.cpp create mode 100644 src/vm/fptrstubs.h create mode 100644 src/vm/frames.cpp create mode 100644 src/vm/frames.h create mode 100644 src/vm/frameworkexceptionloader.cpp create mode 100644 src/vm/frameworkexceptionloader.h create mode 100644 src/vm/fusionbind.cpp create mode 100644 src/vm/fusioninit.cpp create mode 100644 src/vm/fusionsink.cpp create mode 100644 src/vm/gc.h create mode 100644 src/vm/gccover.cpp create mode 100644 src/vm/gccover.h create mode 100644 src/vm/gcdecode.cpp create mode 100644 src/vm/gcdesc.h create mode 100644 src/vm/gcenv.ee.cpp create mode 100644 src/vm/gcenv.ee.h create mode 100644 src/vm/gcenv.h create mode 100644 src/vm/gcenv.interlocked.h create mode 100644 src/vm/gcenv.interlocked.inl create mode 100644 src/vm/gcenv.os.cpp create mode 100644 src/vm/gcenv.os.h create mode 100644 src/vm/gchelpers.cpp create mode 100644 src/vm/gchelpers.h create mode 100644 src/vm/gchost.cpp create mode 100644 src/vm/gcinfodecoder.cpp create mode 100644 src/vm/gcscan.h create mode 100644 src/vm/gcstress.h create mode 100644 src/vm/gdbjit.cpp create mode 100644 src/vm/gdbjit.h create mode 100644 src/vm/gdbjithelpers.h create mode 100644 src/vm/genericdict.cpp create mode 100644 src/vm/genericdict.h create mode 100644 src/vm/generics.cpp create mode 100644 src/vm/generics.h create mode 100644 src/vm/generics.inl create mode 100644 src/vm/genmeth.cpp create mode 100644 src/vm/gms.h create mode 100644 src/vm/h2inc.pl create mode 100644 src/vm/h2inc.ps1 create mode 100644 src/vm/handletable.h create mode 100644 src/vm/handletable.inl create mode 100644 src/vm/hash.cpp create mode 100644 src/vm/hash.h create mode 100644 src/vm/hillclimbing.cpp create mode 100644 src/vm/hillclimbing.h create mode 100644 src/vm/hostexecutioncontext.cpp create mode 100644 src/vm/hostexecutioncontext.h create mode 100644 src/vm/hosting.cpp create mode 100644 src/vm/hosting.h create mode 100644 src/vm/i386/.gitmirror create mode 100644 src/vm/i386/CLRErrorReporting.vrg create mode 100644 src/vm/i386/RedirectedHandledJITCase.asm create mode 100644 src/vm/i386/asmconstants.h create mode 100644 src/vm/i386/asmhelpers.asm create mode 100644 src/vm/i386/cgencpu.h create mode 100644 src/vm/i386/cgenx86.cpp create mode 100644 src/vm/i386/excepcpu.h create mode 100644 src/vm/i386/excepx86.cpp create mode 100644 src/vm/i386/fptext.asm create mode 100644 src/vm/i386/gmsasm.asm create mode 100644 src/vm/i386/gmscpu.h create mode 100644 src/vm/i386/gmsx86.cpp create mode 100644 src/vm/i386/jithelp.asm create mode 100644 src/vm/i386/jitinterfacex86.cpp create mode 100644 src/vm/i386/profiler.cpp create mode 100644 src/vm/i386/remotingx86.cpp create mode 100644 src/vm/i386/stublinkerx86.cpp create mode 100644 src/vm/i386/stublinkerx86.h create mode 100644 src/vm/i386/virtualcallstubcpu.hpp create mode 100644 src/vm/ibclogger.cpp create mode 100644 src/vm/ibclogger.h create mode 100644 src/vm/ildump.h create mode 100644 src/vm/ilmarshalers.cpp create mode 100644 src/vm/ilmarshalers.h create mode 100644 src/vm/ilstubcache.cpp create mode 100644 src/vm/ilstubcache.h create mode 100644 src/vm/ilstubresolver.cpp create mode 100644 src/vm/ilstubresolver.h create mode 100644 src/vm/inlinetracking.cpp create mode 100644 src/vm/inlinetracking.h create mode 100644 src/vm/instmethhash.cpp create mode 100644 src/vm/instmethhash.h create mode 100644 src/vm/interopconverter.cpp create mode 100644 src/vm/interopconverter.h create mode 100644 src/vm/interoputil.cpp create mode 100644 src/vm/interoputil.h create mode 100644 src/vm/interoputil.inl create mode 100644 src/vm/interpreter.cpp create mode 100644 src/vm/interpreter.h create mode 100644 src/vm/interpreter.hpp create mode 100644 src/vm/invalidoverlappedwrappers.h create mode 100644 src/vm/invokeutil.cpp create mode 100644 src/vm/invokeutil.h create mode 100644 src/vm/iterator_util.h create mode 100644 src/vm/jithelpers.cpp create mode 100644 src/vm/jitinterface.cpp create mode 100644 src/vm/jitinterface.h create mode 100644 src/vm/jitinterfacegen.cpp create mode 100644 src/vm/jupiterobject.h create mode 100644 src/vm/listlock.cpp create mode 100644 src/vm/listlock.h create mode 100644 src/vm/listlock.inl create mode 100644 src/vm/loaderallocator.cpp create mode 100644 src/vm/loaderallocator.hpp create mode 100644 src/vm/loaderallocator.inl create mode 100644 src/vm/managedmdimport.cpp create mode 100644 src/vm/managedmdimport.hpp create mode 100644 src/vm/marshalnative.cpp create mode 100644 src/vm/marshalnative.h create mode 100644 src/vm/marvin32.cpp create mode 100644 src/vm/mda.cpp create mode 100644 src/vm/mda.h create mode 100644 src/vm/mda.inl create mode 100644 src/vm/mdaBoilerplate.exe.mda.config create mode 100644 src/vm/mdaassistants.cpp create mode 100644 src/vm/mdaassistants.h create mode 100644 src/vm/mdaassistantschemas.inl create mode 100644 src/vm/mdadac.cpp create mode 100644 src/vm/mdagroups.inl create mode 100644 src/vm/mdamacroscrubber.inl create mode 100644 src/vm/mdaschema.inl create mode 100644 src/vm/memberload.cpp create mode 100644 src/vm/memberload.h create mode 100644 src/vm/message.cpp create mode 100644 src/vm/message.h create mode 100644 src/vm/metasig.h create mode 100644 src/vm/method.cpp create mode 100644 src/vm/method.hpp create mode 100644 src/vm/method.inl create mode 100644 src/vm/methodimpl.cpp create mode 100644 src/vm/methodimpl.h create mode 100644 src/vm/methoditer.cpp create mode 100644 src/vm/methoditer.h create mode 100644 src/vm/methodtable.cpp create mode 100644 src/vm/methodtable.h create mode 100644 src/vm/methodtable.inl create mode 100644 src/vm/methodtablebuilder.cpp create mode 100644 src/vm/methodtablebuilder.h create mode 100644 src/vm/methodtablebuilder.inl create mode 100644 src/vm/microsoft.comservices.h create mode 100644 src/vm/microsoft.comservices_i.c create mode 100644 src/vm/mixedmode.cpp create mode 100644 src/vm/mixedmode.hpp create mode 100644 src/vm/mlinfo.cpp create mode 100644 src/vm/mlinfo.h create mode 100644 src/vm/mngstdinterfaces.cpp create mode 100644 src/vm/mngstdinterfaces.h create mode 100644 src/vm/mngstditflist.h create mode 100644 src/vm/mscorlib.cpp create mode 100644 src/vm/mscorlib.h create mode 100644 src/vm/mscoruefwrapper.h create mode 100644 src/vm/mtypes.h create mode 100644 src/vm/multicorejit.cpp create mode 100644 src/vm/multicorejit.h create mode 100644 src/vm/multicorejitimpl.h create mode 100644 src/vm/multicorejitplayer.cpp create mode 100644 src/vm/namespace.h create mode 100644 src/vm/nativeeventsource.cpp create mode 100644 src/vm/nativeeventsource.h create mode 100644 src/vm/nativeformatreader.h create mode 100644 src/vm/nativeoverlapped.cpp create mode 100644 src/vm/nativeoverlapped.h create mode 100644 src/vm/newcompressedstack.cpp create mode 100644 src/vm/newcompressedstack.h create mode 100644 src/vm/ngenhash.h create mode 100644 src/vm/ngenhash.inl create mode 100644 src/vm/ngenoptout.cpp create mode 100644 src/vm/ngenoptout.h create mode 100644 src/vm/notifyexternals.cpp create mode 100644 src/vm/notifyexternals.h create mode 100644 src/vm/nsenumhandleallcases.h create mode 100644 src/vm/nsenums.h create mode 100644 src/vm/object.cpp create mode 100644 src/vm/object.h create mode 100644 src/vm/object.inl create mode 100644 src/vm/objectclone.cpp create mode 100644 src/vm/objectclone.h create mode 100644 src/vm/objecthandle.h create mode 100644 src/vm/objectlist.cpp create mode 100644 src/vm/objectlist.h create mode 100644 src/vm/olecontexthelpers.cpp create mode 100644 src/vm/olecontexthelpers.h create mode 100644 src/vm/oletls.h create mode 100644 src/vm/olevariant.cpp create mode 100644 src/vm/olevariant.h create mode 100644 src/vm/packedfields.inl create mode 100644 src/vm/pefile.cpp create mode 100644 src/vm/pefile.h create mode 100644 src/vm/pefile.inl create mode 100644 src/vm/pefingerprint.cpp create mode 100644 src/vm/pefingerprint.h create mode 100644 src/vm/peimage.cpp create mode 100644 src/vm/peimage.h create mode 100644 src/vm/peimage.inl create mode 100644 src/vm/peimagelayout.cpp create mode 100644 src/vm/peimagelayout.h create mode 100644 src/vm/peimagelayout.inl create mode 100644 src/vm/pendingload.cpp create mode 100644 src/vm/pendingload.h create mode 100644 src/vm/perfdefaults.cpp create mode 100644 src/vm/perfdefaults.h create mode 100644 src/vm/perfinfo.cpp create mode 100644 src/vm/perfinfo.h create mode 100644 src/vm/perfmap.cpp create mode 100644 src/vm/perfmap.h create mode 100644 src/vm/precode.cpp create mode 100644 src/vm/precode.h create mode 100644 src/vm/prestub.cpp create mode 100644 src/vm/profattach.cpp create mode 100644 src/vm/profattach.h create mode 100644 src/vm/profattach.inl create mode 100644 src/vm/profattachclient.cpp create mode 100644 src/vm/profattachclient.h create mode 100644 src/vm/profattachserver.cpp create mode 100644 src/vm/profattachserver.h create mode 100644 src/vm/profattachserver.inl create mode 100644 src/vm/profdetach.cpp create mode 100644 src/vm/profdetach.h create mode 100644 src/vm/profilermetadataemitvalidator.cpp create mode 100644 src/vm/profilermetadataemitvalidator.h create mode 100644 src/vm/profilingenumerators.cpp create mode 100644 src/vm/profilingenumerators.h create mode 100644 src/vm/profilinghelper.cpp create mode 100644 src/vm/profilinghelper.h create mode 100644 src/vm/profilinghelper.inl create mode 100644 src/vm/proftoeeinterfaceimpl.cpp create mode 100644 src/vm/proftoeeinterfaceimpl.h create mode 100644 src/vm/proftoeeinterfaceimpl.inl create mode 100644 src/vm/qcall.cpp create mode 100644 src/vm/qcall.h create mode 100644 src/vm/rcwrefcache.cpp create mode 100644 src/vm/rcwrefcache.h create mode 100644 src/vm/rcwwalker.cpp create mode 100644 src/vm/rcwwalker.h create mode 100644 src/vm/readytoruninfo.cpp create mode 100644 src/vm/readytoruninfo.h create mode 100644 src/vm/reflectclasswriter.cpp create mode 100644 src/vm/reflectclasswriter.h create mode 100644 src/vm/reflectioninvocation.cpp create mode 100644 src/vm/reflectioninvocation.h create mode 100644 src/vm/registration.h create mode 100644 src/vm/rejit.cpp create mode 100644 src/vm/rejit.h create mode 100644 src/vm/rejit.inl create mode 100644 src/vm/remoting.cpp create mode 100644 src/vm/remoting.h create mode 100644 src/vm/rexcep.h create mode 100644 src/vm/rtlfunctions.cpp create mode 100644 src/vm/rtlfunctions.h create mode 100644 src/vm/runtimecallablewrapper.cpp create mode 100644 src/vm/runtimecallablewrapper.h create mode 100644 src/vm/runtimeexceptionkind.h create mode 100644 src/vm/runtimehandles.cpp create mode 100644 src/vm/runtimehandles.h create mode 100644 src/vm/rwlock.cpp create mode 100644 src/vm/rwlock.h create mode 100644 src/vm/safehandle.cpp create mode 100644 src/vm/security.cpp create mode 100644 src/vm/security.h create mode 100644 src/vm/security.inl create mode 100644 src/vm/securityattributes.cpp create mode 100644 src/vm/securityattributes.h create mode 100644 src/vm/securityattributes.inl create mode 100644 src/vm/securityconfig.cpp create mode 100644 src/vm/securityconfig.h create mode 100644 src/vm/securitydeclarative.cpp create mode 100644 src/vm/securitydeclarative.h create mode 100644 src/vm/securitydeclarative.inl create mode 100644 src/vm/securitydeclarativecache.cpp create mode 100644 src/vm/securitydeclarativecache.h create mode 100644 src/vm/securitydescriptor.cpp create mode 100644 src/vm/securitydescriptor.h create mode 100644 src/vm/securitydescriptor.inl create mode 100644 src/vm/securitydescriptorappdomain.cpp create mode 100644 src/vm/securitydescriptorappdomain.h create mode 100644 src/vm/securitydescriptorappdomain.inl create mode 100644 src/vm/securitydescriptorassembly.cpp create mode 100644 src/vm/securitydescriptorassembly.h create mode 100644 src/vm/securitydescriptorassembly.inl create mode 100644 src/vm/securityhostprotection.cpp create mode 100644 src/vm/securityhostprotection.h create mode 100644 src/vm/securityimperative.cpp create mode 100644 src/vm/securityimperative.h create mode 100644 src/vm/securitymeta.cpp create mode 100644 src/vm/securitymeta.h create mode 100644 src/vm/securitymeta.inl create mode 100644 src/vm/securitypolicy.cpp create mode 100644 src/vm/securitypolicy.h create mode 100644 src/vm/securityprincipal.cpp create mode 100644 src/vm/securityprincipal.h create mode 100644 src/vm/securitystackwalk.cpp create mode 100644 src/vm/securitystackwalk.h create mode 100644 src/vm/securitytransparentassembly.cpp create mode 100644 src/vm/securitytransparentassembly.h create mode 100644 src/vm/securitytransparentassembly.inl create mode 100644 src/vm/sha1.cpp create mode 100644 src/vm/sha1.h create mode 100644 src/vm/sigformat.cpp create mode 100644 src/vm/sigformat.h create mode 100644 src/vm/siginfo.cpp create mode 100644 src/vm/siginfo.hpp create mode 100644 src/vm/simplerwlock.cpp create mode 100644 src/vm/simplerwlock.hpp create mode 100644 src/vm/sourceline.cpp create mode 100644 src/vm/sourceline.h create mode 100644 src/vm/specialstatics.h create mode 100644 src/vm/spinlock.cpp create mode 100644 src/vm/spinlock.h create mode 100644 src/vm/stackbuildersink.cpp create mode 100644 src/vm/stackbuildersink.h create mode 100644 src/vm/stackcompressor.cpp create mode 100644 src/vm/stackcompressor.h create mode 100644 src/vm/stackingallocator.cpp create mode 100644 src/vm/stackingallocator.h create mode 100644 src/vm/stackprobe.cpp create mode 100644 src/vm/stackprobe.h create mode 100644 src/vm/stackprobe.inl create mode 100644 src/vm/stacksampler.cpp create mode 100644 src/vm/stacksampler.h create mode 100644 src/vm/stackwalk.cpp create mode 100644 src/vm/stackwalk.h create mode 100644 src/vm/stackwalktypes.h create mode 100644 src/vm/staticallocationhelpers.inl create mode 100644 src/vm/stdinterfaces.cpp create mode 100644 src/vm/stdinterfaces.h create mode 100644 src/vm/stdinterfaces_internal.h create mode 100644 src/vm/stdinterfaces_wrapper.cpp create mode 100644 src/vm/stringliteralmap.cpp create mode 100644 src/vm/stringliteralmap.h create mode 100644 src/vm/stubcache.cpp create mode 100644 src/vm/stubcache.h create mode 100644 src/vm/stubgen.cpp create mode 100644 src/vm/stubgen.h create mode 100644 src/vm/stubhelpers.cpp create mode 100644 src/vm/stubhelpers.h create mode 100644 src/vm/stublink.cpp create mode 100644 src/vm/stublink.h create mode 100644 src/vm/stublink.inl create mode 100644 src/vm/stubmgr.cpp create mode 100644 src/vm/stubmgr.h create mode 100644 src/vm/syncblk.cpp create mode 100644 src/vm/syncblk.h create mode 100644 src/vm/syncblk.inl create mode 100644 src/vm/syncclean.cpp create mode 100644 src/vm/syncclean.hpp create mode 100644 src/vm/synch.cpp create mode 100644 src/vm/synch.h create mode 100644 src/vm/synchronizationcontextnative.cpp create mode 100644 src/vm/synchronizationcontextnative.h create mode 100644 src/vm/testhookmgr.cpp create mode 100644 src/vm/testhookmgr.h create mode 100644 src/vm/threaddebugblockinginfo.cpp create mode 100644 src/vm/threaddebugblockinginfo.h create mode 100644 src/vm/threadpoolrequest.cpp create mode 100644 src/vm/threadpoolrequest.h create mode 100644 src/vm/threads.cpp create mode 100644 src/vm/threads.h create mode 100644 src/vm/threads.inl create mode 100644 src/vm/threadstatics.cpp create mode 100644 src/vm/threadstatics.h create mode 100644 src/vm/threadsuspend.cpp create mode 100644 src/vm/threadsuspend.h create mode 100644 src/vm/tlbexport.cpp create mode 100644 src/vm/tlbexport.h create mode 100644 src/vm/typectxt.cpp create mode 100644 src/vm/typectxt.h create mode 100644 src/vm/typedesc.cpp create mode 100644 src/vm/typedesc.h create mode 100644 src/vm/typedesc.inl create mode 100644 src/vm/typeequivalencehash.cpp create mode 100644 src/vm/typeequivalencehash.hpp create mode 100644 src/vm/typehandle.cpp create mode 100644 src/vm/typehandle.h create mode 100644 src/vm/typehandle.inl create mode 100644 src/vm/typehash.cpp create mode 100644 src/vm/typehash.h create mode 100644 src/vm/typehashingalgorithms.h create mode 100644 src/vm/typekey.h create mode 100644 src/vm/typeparse.cpp create mode 100644 src/vm/typeparse.h create mode 100644 src/vm/typestring.cpp create mode 100644 src/vm/typestring.h create mode 100644 src/vm/umthunkhash.cpp create mode 100644 src/vm/umthunkhash.h create mode 100644 src/vm/util.cpp create mode 100644 src/vm/util.hpp create mode 100644 src/vm/validator.cpp create mode 100644 src/vm/vars.cpp create mode 100644 src/vm/vars.hpp create mode 100644 src/vm/verifier.cpp create mode 100644 src/vm/verifier.hpp create mode 100644 src/vm/veropcodes.hpp create mode 100644 src/vm/versionresilienthashcode.cpp create mode 100644 src/vm/versionresilienthashcode.h create mode 100644 src/vm/vertable.h create mode 100644 src/vm/virtualcallstub.cpp create mode 100644 src/vm/virtualcallstub.h create mode 100644 src/vm/vm.settings create mode 100644 src/vm/vm.targets create mode 100644 src/vm/vmholder.h create mode 100644 src/vm/weakreferencenative.cpp create mode 100644 src/vm/weakreferencenative.h create mode 100644 src/vm/win32threadpool.cpp create mode 100644 src/vm/win32threadpool.h create mode 100644 src/vm/winrthelpers.cpp create mode 100644 src/vm/winrthelpers.h create mode 100644 src/vm/winrtredirector.h create mode 100644 src/vm/winrtredirector.inl create mode 100644 src/vm/winrttypenameconverter.cpp create mode 100644 src/vm/winrttypenameconverter.h create mode 100644 src/vm/wks/.gitmirror create mode 100644 src/vm/wks/CMakeLists.txt create mode 100644 src/vm/wks/wks.nativeproj create mode 100644 src/vm/wks/wks.targets create mode 100644 src/vm/wrappers.h create mode 100644 src/vm/zapsig.cpp create mode 100644 src/vm/zapsig.h (limited to 'src/vm') diff --git a/src/vm/.gitmirror b/src/vm/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/vm/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file diff --git a/src/vm/CMakeLists.txt b/src/vm/CMakeLists.txt new file mode 100644 index 0000000000..6f17a90c1f --- /dev/null +++ b/src/vm/CMakeLists.txt @@ -0,0 +1,467 @@ +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# Needed due to the cmunged files being in the binary folders, the set(CMAKE_INCLUDE_CURRENT_DIR ON) is not enough +include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}) + +include_directories(${CLR_DIR}/src/gc) + +include_directories(${ARCH_SOURCES_DIR}) + +add_definitions(-DFEATURE_LEAVE_RUNTIME_HOLDER=1) + +add_definitions(-DUNICODE) +add_definitions(-D_UNICODE) + + +if(CMAKE_CONFIGURATION_TYPES) # multi-configuration generator? + foreach (Config DEBUG CHECKED) + set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS $<$:WRITE_BARRIER_CHECK=1>) + endforeach (Config) +else() + if(UPPERCASE_CMAKE_BUILD_TYPE STREQUAL DEBUG OR UPPERCASE_CMAKE_BUILD_TYPE STREQUAL CHECKED) + add_definitions(-DWRITE_BARRIER_CHECK=1) + endif(UPPERCASE_CMAKE_BUILD_TYPE STREQUAL DEBUG OR UPPERCASE_CMAKE_BUILD_TYPE STREQUAL CHECKED) +endif(CMAKE_CONFIGURATION_TYPES) + +if(CLR_CMAKE_PLATFORM_UNIX) + add_compile_options(-fPIC) +endif(CLR_CMAKE_PLATFORM_UNIX) + +if(FEATURE_GDBJIT) + set(VM_SOURCES_GDBJIT + gdbjit.cpp + ) + add_definitions(-DFEATURE_GDBJIT) +endif(FEATURE_GDBJIT) + +set(VM_SOURCES_DAC_AND_WKS_COMMON + appdomain.cpp + array.cpp + assembly.cpp + baseassemblyspec.cpp + binder.cpp + ceeload.cpp + class.cpp + classhash.cpp + clsload.cpp + codeman.cpp + comdelegate.cpp + contractimpl.cpp + coreassemblyspec.cpp + corebindresult.cpp + corhost.cpp + crst.cpp + debugdebugger.cpp + debughelp.cpp + debuginfostore.cpp + decodemd.cpp + disassembler.cpp + dllimport.cpp + domainfile.cpp + dynamicmethod.cpp + ecall.cpp + eedbginterfaceimpl.cpp + eehash.cpp + eetwain.cpp + encee.cpp + excep.cpp + exstate.cpp + field.cpp + formattype.cpp + fptrstubs.cpp + frames.cpp + genericdict.cpp + generics.cpp + hash.cpp + hillclimbing.cpp + ilstubcache.cpp + ilstubresolver.cpp + inlinetracking.cpp + instmethhash.cpp + jitinterface.cpp + loaderallocator.cpp + memberload.cpp + method.cpp + methodimpl.cpp + methoditer.cpp + methodtable.cpp + object.cpp + pefile.cpp + peimage.cpp + peimagelayout.cpp + perfmap.cpp + perfinfo.cpp + precode.cpp + prestub.cpp + rejit.cpp + securitydescriptor.cpp + securitydescriptorassembly.cpp + sigformat.cpp + siginfo.cpp + stackwalk.cpp + stublink.cpp + stubmgr.cpp + syncblk.cpp + threadpoolrequest.cpp + threads.cpp + threadstatics.cpp + typectxt.cpp + typedesc.cpp + typehandle.cpp + typehash.cpp + typestring.cpp + util.cpp + vars.cpp + versionresilienthashcode.cpp + virtualcallstub.cpp + win32threadpool.cpp + zapsig.cpp + ${VM_SOURCES_GDBJIT} +) + +if(FEATURE_READYTORUN) + list(APPEND VM_SOURCES_DAC_AND_WKS_COMMON + readytoruninfo.cpp + ) +endif(FEATURE_READYTORUN) + +set(VM_SOURCES_DAC + ${VM_SOURCES_DAC_AND_WKS_COMMON} + contexts.cpp + threaddebugblockinginfo.cpp +) + +set(VM_SOURCES_WKS + ${VM_SOURCES_DAC_AND_WKS_COMMON} + appdomainnative.cpp + appdomainstack.cpp + assemblyname.cpp + assemblynative.cpp + assemblyspec.cpp + cachelinealloc.cpp + callhelpers.cpp + ceemain.cpp + clrex.cpp + clrprivbinderutil.cpp + clrvarargs.cpp + comdatetime.cpp + comdependenthandle.cpp + comdynamic.cpp + comisolatedstorage.cpp + commemoryfailpoint.cpp + commodule.cpp + compatibilityswitch.cpp + comsynchronizable.cpp + comthreadpool.cpp + comutilnative.cpp + comwaithandle.cpp + constrainedexecutionregion.cpp + coverage.cpp + customattribute.cpp + custommarshalerinfo.cpp + dllimportcallback.cpp + eeconfig.cpp + eecontract.cpp + eemessagebox.cpp + eepolicy.cpp + eetoprofinterfaceimpl.cpp + eventstore.cpp + fcall.cpp + fieldmarshaler.cpp + finalizerthread.cpp + frameworkexceptionloader.cpp + gccover.cpp + gcenv.ee.cpp + gcenv.os.cpp + gchelpers.cpp + genmeth.cpp + hosting.cpp + ibclogger.cpp + ilmarshalers.cpp + interopconverter.cpp + interoputil.cpp + interpreter.cpp + invokeutil.cpp + jithelpers.cpp + listlock.cpp + managedmdimport.cpp + marshalnative.cpp + marvin32.cpp + mdaassistants.cpp + methodtablebuilder.cpp + mlinfo.cpp + mscorlib.cpp # true + multicorejit.cpp # Condition="'$(FeatureMulticoreJIT)' == 'true' + multicorejitplayer.cpp # Condition="'$(FeatureMulticoreJIT)' == 'true' + nativeeventsource.cpp + nativeoverlapped.cpp + objectlist.cpp + olevariant.cpp + pefingerprint.cpp + pendingload.cpp + perfdefaults.cpp + profattach.cpp + profattachclient.cpp + profattachserver.cpp + profdetach.cpp + profilermetadataemitvalidator.cpp + profilingenumerators.cpp + profilinghelper.cpp + proftoeeinterfaceimpl.cpp + qcall.cpp + reflectclasswriter.cpp + reflectioninvocation.cpp + runtimehandles.cpp + safehandle.cpp + security.cpp + securityattributes.cpp + securitydeclarative.cpp + securitydeclarativecache.cpp + securitydescriptorappdomain.cpp + securityhostprotection.cpp + securitymeta.cpp + securitypolicy.cpp + securitytransparentassembly.cpp + sha1.cpp + simplerwlock.cpp + sourceline.cpp + spinlock.cpp + stackingallocator.cpp + stringliteralmap.cpp + stubcache.cpp + stubgen.cpp + stubhelpers.cpp + syncclean.cpp + synch.cpp + synchronizationcontextnative.cpp + testhookmgr.cpp + threaddebugblockinginfo.cpp + threadsuspend.cpp + typeparse.cpp + verifier.cpp + weakreferencenative.cpp +) + +if(FEATURE_EVENT_TRACE) + list(APPEND VM_SOURCES_WKS + eventtrace.cpp + ) +endif(FEATURE_EVENT_TRACE) + +if(WIN32) + +set(VM_SOURCES_DAC_AND_WKS_WIN32 + clrtocomcall.cpp + rcwwalker.cpp + winrttypenameconverter.cpp +) + +list(APPEND VM_SOURCES_WKS + ${VM_SOURCES_DAC_AND_WKS_WIN32} + # These should not be included for Linux + appxutil.cpp + assemblynativeresource.cpp + classcompat.cpp + classfactory.cpp + clrprivbinderwinrt.cpp + clrprivtypecachewinrt.cpp + comcache.cpp + comcallablewrapper.cpp + comconnectionpoints.cpp + cominterfacemarshaler.cpp + commtmemberinfomap.cpp + comtoclrcall.cpp + dispatchinfo.cpp + dispparammarshaler.cpp + dwreport.cpp + eventreporter.cpp + extensibleclassfactory.cpp + microsoft.comservices_i.c + mngstdinterfaces.cpp + notifyexternals.cpp + olecontexthelpers.cpp + rcwrefcache.cpp + rtlfunctions.cpp + runtimecallablewrapper.cpp + securityprincipal.cpp + stacksampler.cpp + stdinterfaces.cpp + stdinterfaces_wrapper.cpp + winrthelpers.cpp +) + +list(APPEND VM_SOURCES_DAC + ${VM_SOURCES_DAC_AND_WKS_WIN32} + # These should not be included for Linux + clrprivbinderwinrt.cpp + clrprivtypecachewinrt.cpp +) + +if(CLR_CMAKE_TARGET_ARCH_AMD64) + set(VM_SOURCES_WKS_ARCH_ASM + ${ARCH_SOURCES_DIR}/AsmHelpers.asm + ${ARCH_SOURCES_DIR}/CallDescrWorkerAMD64.asm + ${ARCH_SOURCES_DIR}/ComCallPreStub.asm + ${ARCH_SOURCES_DIR}/CrtHelpers.asm + ${ARCH_SOURCES_DIR}/GenericComCallStubs.asm + ${ARCH_SOURCES_DIR}/GenericComPlusCallStubs.asm + ${ARCH_SOURCES_DIR}/getstate.asm + ${ARCH_SOURCES_DIR}/InstantiatingStub.asm + ${ARCH_SOURCES_DIR}/JitHelpers_Fast.asm + ${ARCH_SOURCES_DIR}/JitHelpers_FastWriteBarriers.asm + ${ARCH_SOURCES_DIR}/JitHelpers_InlineGetAppDomain.asm + ${ARCH_SOURCES_DIR}/JitHelpers_InlineGetThread.asm + ${ARCH_SOURCES_DIR}/JitHelpers_Slow.asm + ${ARCH_SOURCES_DIR}/PInvokeStubs.asm + ${ARCH_SOURCES_DIR}/RedirectedHandledJITCase.asm + ${ARCH_SOURCES_DIR}/ThePreStubAMD64.asm + ${ARCH_SOURCES_DIR}/ExternalMethodFixupThunk.asm + ${ARCH_SOURCES_DIR}/TlsGetters.asm # Condition="'$(FeatureImplicitTls)' != 'true' + ${ARCH_SOURCES_DIR}/UMThunkStub.asm + ${ARCH_SOURCES_DIR}/VirtualCallStubAMD64.asm + ) +elseif(CLR_CMAKE_TARGET_ARCH_I386) + set(VM_SOURCES_WKS_ARCH_ASM + ${ARCH_SOURCES_DIR}/RedirectedHandledJITCase.asm + ${ARCH_SOURCES_DIR}/asmhelpers.asm + ${ARCH_SOURCES_DIR}/fptext.asm + ${ARCH_SOURCES_DIR}/gmsasm.asm + ${ARCH_SOURCES_DIR}/jithelp.asm + ) +elseif(CLR_CMAKE_TARGET_ARCH_ARM) + set(VM_SOURCES_WKS_ARCH_ASM + ${ARCH_SOURCES_DIR}/asmhelpers.asm + ${ARCH_SOURCES_DIR}/CrtHelpers.asm + ${ARCH_SOURCES_DIR}/ehhelpers.asm + ${ARCH_SOURCES_DIR}/memcpy.asm + ${ARCH_SOURCES_DIR}/patchedcode.asm + ${ARCH_SOURCES_DIR}/PInvokeStubs.asm + ) +elseif(CLR_CMAKE_TARGET_ARCH_ARM64) + set(VM_SOURCES_WKS_ARCH_ASM + ${ARCH_SOURCES_DIR}/AsmHelpers.asm + ${ARCH_SOURCES_DIR}/CallDescrWorkerARM64.asm + ${ARCH_SOURCES_DIR}/CrtHelpers.asm + ${ARCH_SOURCES_DIR}/PInvokeStubs.asm + ) + +endif() + +else(WIN32) + + if(CLR_CMAKE_TARGET_ARCH_AMD64) + set(VM_SOURCES_WKS_ARCH_ASM + ${ARCH_SOURCES_DIR}/calldescrworkeramd64.S + ${ARCH_SOURCES_DIR}/crthelpers.S + ${ARCH_SOURCES_DIR}/externalmethodfixupthunk.S + ${ARCH_SOURCES_DIR}/getstate.S + ${ARCH_SOURCES_DIR}/jithelpers_fast.S + ${ARCH_SOURCES_DIR}/jithelpers_fastwritebarriers.S + ${ARCH_SOURCES_DIR}/jithelpers_slow.S + ${ARCH_SOURCES_DIR}/pinvokestubs.S + ${ARCH_SOURCES_DIR}/theprestubamd64.S + ${ARCH_SOURCES_DIR}/unixasmhelpers.S + ${ARCH_SOURCES_DIR}/umthunkstub.S + ${ARCH_SOURCES_DIR}/virtualcallstubamd64.S + ) + elseif(CLR_CMAKE_TARGET_ARCH_ARM) + set(VM_SOURCES_WKS_ARCH_ASM + ${ARCH_SOURCES_DIR}/asmhelpers.S + ${ARCH_SOURCES_DIR}/crthelpers.S + ${ARCH_SOURCES_DIR}/ehhelpers.S + ${ARCH_SOURCES_DIR}/memcpy.S + ${ARCH_SOURCES_DIR}/patchedcode.S + ${ARCH_SOURCES_DIR}/pinvokestubs.S + ) + elseif(CLR_CMAKE_TARGET_ARCH_ARM64) + set(VM_SOURCES_WKS_ARCH_ASM + ${ARCH_SOURCES_DIR}/asmhelpers.S + ${ARCH_SOURCES_DIR}/calldescrworkerarm64.S + ${ARCH_SOURCES_DIR}/crthelpers.S + ${ARCH_SOURCES_DIR}/pinvokestubs.S + ) + endif() + +endif(WIN32) + + +if(CLR_CMAKE_TARGET_ARCH_AMD64) + set(VM_SOURCES_DAC_AND_WKS_ARCH + ${ARCH_SOURCES_DIR}/cgenamd64.cpp + ${ARCH_SOURCES_DIR}/excepamd64.cpp + ${ARCH_SOURCES_DIR}/gmsamd64.cpp + ${ARCH_SOURCES_DIR}/stublinkeramd64.cpp + ) + + set(VM_SOURCES_WKS_ARCH + ${ARCH_SOURCES_DIR}/jithelpersamd64.cpp + ${ARCH_SOURCES_DIR}/jitinterfaceamd64.cpp + ${ARCH_SOURCES_DIR}/profiler.cpp + exceptionhandling.cpp + gcinfodecoder.cpp + jitinterfacegen.cpp + ) +elseif(CLR_CMAKE_TARGET_ARCH_I386) + set(VM_SOURCES_DAC_AND_WKS_ARCH + gcdecode.cpp + exinfo.cpp + ${ARCH_SOURCES_DIR}/cgenx86.cpp + ${ARCH_SOURCES_DIR}/excepx86.cpp + ${ARCH_SOURCES_DIR}/gmsx86.cpp + ${ARCH_SOURCES_DIR}/stublinkerx86.cpp + ) + + set(VM_SOURCES_WKS_ARCH + ${ARCH_SOURCES_DIR}/jithelp.asm + ${ARCH_SOURCES_DIR}/jitinterfacex86.cpp + ${ARCH_SOURCES_DIR}/profiler.cpp + ) +elseif(CLR_CMAKE_TARGET_ARCH_ARM) + set(VM_SOURCES_DAC_AND_WKS_ARCH + ${ARCH_SOURCES_DIR}/exceparm.cpp + ${ARCH_SOURCES_DIR}/stubs.cpp + ${ARCH_SOURCES_DIR}/armsinglestepper.cpp + ) + + set(VM_SOURCES_WKS_ARCH + ${ARCH_SOURCES_DIR}/jithelpersarm.cpp + ${ARCH_SOURCES_DIR}/profiler.cpp + exceptionhandling.cpp + gcinfodecoder.cpp + ) +elseif(CLR_CMAKE_TARGET_ARCH_ARM64) + set(VM_SOURCES_DAC_AND_WKS_ARCH + ${ARCH_SOURCES_DIR}/cgenarm64.cpp + ${ARCH_SOURCES_DIR}/stubs.cpp + exceptionhandling.cpp + gcinfodecoder.cpp + ) +endif() + +if(CLR_CMAKE_PLATFORM_UNIX) + list(APPEND VM_SOURCES_WKS_ARCH + ${ARCH_SOURCES_DIR}/unixstubs.cpp + ) +endif(CLR_CMAKE_PLATFORM_UNIX) + +set(VM_SOURCES_DAC_ARCH + gcinfodecoder.cpp + exceptionhandling.cpp +) + +list(APPEND VM_SOURCES_WKS + ${VM_SOURCES_WKS_ARCH} + ${VM_SOURCES_DAC_AND_WKS_ARCH} +) + +list(APPEND VM_SOURCES_DAC + ${VM_SOURCES_DAC_ARCH} + ${VM_SOURCES_DAC_AND_WKS_ARCH} +) + +convert_to_absolute_path(VM_SOURCES_WKS ${VM_SOURCES_WKS}) +convert_to_absolute_path(VM_SOURCES_WKS_ARCH_ASM ${VM_SOURCES_WKS_ARCH_ASM}) +convert_to_absolute_path(VM_SOURCES_DAC ${VM_SOURCES_DAC}) + +add_subdirectory(dac) +add_subdirectory(wks) diff --git a/src/vm/ClrEtwAll.man b/src/vm/ClrEtwAll.man new file mode 100644 index 0000000000..bbf4ce40fd --- /dev/null +++ b/src/vm/ClrEtwAll.man @@ -0,0 +1,7026 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/vm/ClrEtwAllMeta.lst b/src/vm/ClrEtwAllMeta.lst new file mode 100644 index 0000000000..65b3ac3ca0 --- /dev/null +++ b/src/vm/ClrEtwAllMeta.lst @@ -0,0 +1,607 @@ +# +# This list is to specify the events not supported on Mac. +# The format of this file is :[eventtask]:[eventprovider]:[eventversion]:[eventsymbol] +# where could be one of nostack, nomac, stack, noclrinstanceid +# [eventtask] is the task of the event +# [eventprovider] is the provider of the event +# [eventversion] is the version of the event +# [eventsymbol] is the symbol of the event +# in the src\VM\ClrEtwAll.man manifest file +# +# is mandatory +# * can be used as a wildcard in place of [eventtask], [eventprovider], [eventversion], [eventversion] +# if [eventprovider] is specified, then the action is applied to the entire provider unless [eventtask] or [eventsymbol] is specified +# if [eventtask] is specified, then the action is applied to all the events with that task unless the [eventsymbol] is also specified. [eventprovider] is ignored at this time +# if [eventsymbol] is specified, then the action is applied to only that event. [eventprovider] is ignored at this time +# [eventversion] is currently unused and will act as NOP +# +# If we do not want an event to have a stack, there should be nostack entries for all versions of that event +# An event having a stack is represented by a '1' bit and a '0' bit otherwise +# A single bit is saved for a single event value and therefore even if the event has multiple versions, +# it has a single bit. +# Logical OR rules apply as far as support for stack for an event is concerned, +# which is to say that if an event is marked as 'stack' and 'nostack', the logical OR'ing will give the final result of 'stack' +# +# Whenever a new version of an event comes up such that +# its older version is no longer used on Mac, the older +# version's event entry must be added here +# + +################################## +# Events from the runtime provider +################################## + +################## +# Used in Non Windows Platform Linux +################## +noclrinstanceid::::EventSource + +########################## +# GarbageCollection events +########################## +noclrinstanceid:GarbageCollection:::GCStart +nostack:GarbageCollection:::GCStart +nomac:GarbageCollection:::GCStart_V1 +nostack:GarbageCollection:::GCStart_V1 +nomac:GarbageCollection:::GCStart_V2 +nostack:GarbageCollection:::GCStart_V2 +nomac:GarbageCollection:::GCEnd +noclrinstanceid:GarbageCollection:::GCEnd +nostack:GarbageCollection:::GCEnd +nostack:GarbageCollection:::GCEnd_V1 +nomac:GarbageCollection:::GCRestartEEEnd +noclrinstanceid:GarbageCollection:::GCRestartEEEnd +nostack:GarbageCollection:::GCRestartEEEnd +nostack:GarbageCollection:::GCRestartEEEnd_V1 +nomac:GarbageCollection:::GCHeapStats +noclrinstanceid:GarbageCollection:::GCHeapStats +nostack:GarbageCollection:::GCHeapStats +nostack:GarbageCollection:::GCHeapStats_V1 +nomac:GarbageCollection:::GCHeapStats_V1 +nomac:GarbageCollection:::GCCreateSegment +nostack:GarbageCollection:::GCCreateSegment +noclrinstanceid:GarbageCollection:::GCCreateSegment +nostack:GarbageCollection:::GCCreateSegment_V1 +nomac:GarbageCollection:::GCFreeSegment +noclrinstanceid:GarbageCollection:::GCFreeSegment +nostack:GarbageCollection:::GCFreeSegment +nostack:GarbageCollection:::GCFreeSegment_V1 +nomac:GarbageCollection:::GCRestartEEBegin +noclrinstanceid:GarbageCollection:::GCRestartEEBegin +nostack:GarbageCollection:::GCRestartEEBegin +nostack:GarbageCollection:::GCRestartEEBegin_V1 +nomac:GarbageCollection:::GCSuspendEEEnd +noclrinstanceid:GarbageCollection:::GCSuspendEEEnd +nostack:GarbageCollection:::GCSuspendEEEnd +nostack:GarbageCollection:::GCSuspendEEEnd_V1 +nomac:GarbageCollection:::GCSuspendEEBegin +noclrinstanceid:GarbageCollection:::GCSuspendEEBegin +nostack:GarbageCollection:::GCSuspendEEBegin +nostack:GarbageCollection:::GCSuspendEEBegin_V1 +nomac:GarbageCollection:::GCAllocationTick +noclrinstanceid:GarbageCollection:::GCAllocationTick +nomac:GarbageCollection:::GCCreateConcurrentThread +noclrinstanceid:GarbageCollection:::GCCreateConcurrentThread +nostack:GarbageCollection:::GCCreateConcurrentThread +nostack:GarbageCollection:::GCCreateConcurrentThread_V1 +nomac:GarbageCollection:::GCCreateConcurrentThread_V1 +nomac:GarbageCollection:::GCTerminateConcurrentThread +noclrinstanceid:GarbageCollection:::GCTerminateConcurrentThread +nostack:GarbageCollection:::GCTerminateConcurrentThread +nomac:GarbageCollection:::GCTerminateConcurrentThread_V1 +nostack:GarbageCollection:::GCTerminateConcurrentThread_V1 +nomac:GarbageCollection:::GCFinalizersEnd +noclrinstanceid:GarbageCollection:::GCFinalizersEnd +nostack:GarbageCollection:::GCFinalizersEnd +nostack:GarbageCollection:::GCFinalizersEnd_V1 +nomac:GarbageCollection:::GCFinalizersBegin +noclrinstanceid:GarbageCollection:::GCFinalizersBegin +nostack:GarbageCollection:::GCFinalizersBegin +nostack:GarbageCollection:::GCFinalizersBegin_V1 +nomac:GarbageCollection:::GCMarkStackRoots +nostack:GarbageCollection:::GCMarkStackRoots +nomac:GarbageCollection:::GCMarkFinalizeQueueRoots +nostack:GarbageCollection:::GCMarkFinalizeQueueRoots +nomac:GarbageCollection:::GCMarkHandles +nostack:GarbageCollection:::GCMarkHandles +nomac:GarbageCollection:::GCMarkOlderGenerationRoots +nostack:GarbageCollection:::GCMarkOlderGenerationRoots +nomac:GarbageCollection:::GCMarkWithType +nostack:GarbageCollection:::GCMarkWithType +nostack:GarbageCollection:::PinObjectAtGCTime +nostack:GarbageCollection:::FinalizeObject +nostack:GarbageCollection:::GCGenerationRange +nostack:GarbageCollection:::GCBulkRootEdge +nostack:GarbageCollection:::GCBulkRootConditionalWeakTableElementEdge +nostack:GarbageCollection:::GCBulkNode +nostack:GarbageCollection:::GCBulkEdge +nostack:GarbageCollection:::GCBulkSurvivingObjectRanges +nostack:GarbageCollection:::GCBulkMovedObjectRanges +nostack:GarbageCollection:::GCBulkRootCCW +nostack:GarbageCollection:::GCBulkRCW +nostack:GarbageCollection:::GCBulkRootStaticVar +nomac:GarbageCollection:::GCPerHeapHistory_V3 +nostack:GarbageCollection:::GCPerHeapHistory_V3 +nomac:GarbageCollection:::GCGlobalHeap_V2 +nostack:GarbageCollection:::GCGlobalHeap_V2 +nomac:GarbageCollection:::GCJoin_V2 + +############# +# Type events +############# + +nostack:Type:::BulkType + +################### +# Threadpool events +################### +nomac:WorkerThreadCreation:::WorkerThreadCreate +noclrinstanceid:WorkerThreadCreation:::WorkerThreadCreate +nomac:WorkerThreadCreation:::WorkerThreadTerminate +noclrinstanceid:WorkerThreadCreation:::WorkerThreadTerminate +nomac:WorkerThreadRetirement:::WorkerThreadRetire +noclrinstanceid:WorkerThreadRetirement:::WorkerThreadRetire +nomac:WorkerThreadRetirement:::WorkerThreadUnretire +noclrinstanceid:WorkerThreadRetirement:::WorkerThreadUnretire +nomac:IOThreadCreation:::IOThreadCreate +noclrinstanceid:IOThreadCreation:::IOThreadCreate +nomac:IOThreadCreation:::IOThreadTerminate +noclrinstanceid:IOThreadCreation:::IOThreadTerminate +nomac:IOThreadRetirement:::IOThreadRetire +noclrinstanceid:IOThreadRetirement:::IOThreadRetire +nomac:IOThreadRetirement:::IOThreadUnretire +noclrinstanceid:IOThreadRetirement:::IOThreadUnretire +nomac:ThreadpoolSuspension:::ThreadpoolSuspensionSuspendThread +noclrinstanceid:ThreadpoolSuspension:::ThreadpoolSuspensionSuspendThread +nomac:ThreadpoolSuspension:::ThreadpoolSuspensionResumeThread +noclrinstanceid:ThreadpoolSuspension:::ThreadpoolSuspensionResumeThread +nomac:ThreadPoolWorkerThread:::ThreadPoolWorkerThreadStart +nostack:ThreadPoolWorkerThread:::ThreadPoolWorkerThreadStart +nostack:ThreadPoolWorkerThread:::ThreadPoolWorkerThreadWait +nomac:ThreadPoolWorkerThread:::ThreadPoolWorkerThreadStop +nostack:ThreadPoolWorkerThread:::ThreadPoolWorkerThreadStop +nomac:ThreadPoolWorkerThreadRetirement:::ThreadPoolWorkerThreadRetirementStart +nostack:ThreadPoolWorkerThreadRetirement:::ThreadPoolWorkerThreadRetirementStart +nomac:ThreadPoolWorkerThreadRetirement:::ThreadPoolWorkerThreadRetirementStop +nostack:ThreadPoolWorkerThreadRetirement:::ThreadPoolWorkerThreadRetirementStop +nomac:ThreadPoolWorkerThreadAdjustment:::ThreadPoolWorkerThreadAdjustmentSample +nostack:ThreadPoolWorkerThreadAdjustment:::ThreadPoolWorkerThreadAdjustmentSample +nomac:ThreadPoolWorkerThreadAdjustment:::ThreadPoolWorkerThreadAdjustmentAdjustment +nostack:ThreadPoolWorkerThreadAdjustment:::ThreadPoolWorkerThreadAdjustmentAdjustment + +################## +# Exception events +################## +nomac:Exception:::ExceptionThrown +noclrinstanceid:Exception:::ExceptionThrown + +################### +# Contention events +################### +nomac:Contention:::Contention +noclrinstanceid:Contention:::Contention +nomac:Contention:::ContentionStart_V1 +nostack:Contention:::ContentionStop +nomac:Contention:::ContentionStop + +################## +# StackWalk events +################## +nomac:CLRStack:::CLRStackWalk +nostack:CLRStack:::CLRStackWalk + +#################################### +# AppDomainResourceManagement events +#################################### +nomac:AppDomainResourceManagement:::AppDomainMemAllocated +nomac:AppDomainResourceManagement:::AppDomainMemSurvived +nomac:AppDomainResourceManagement:::ThreadCreated +nomac:AppDomainResourceManagement:::ThreadTerminated +nomac:AppDomainResourceManagement:::ThreadDomainEnter + +################ +# Interop events +################ +nomac:CLRILStub:::ILStubGenerated +nomac:CLRILStub:::ILStubCacheHit + +############### +# Method events +############### +nomac:CLRMethod:::MethodLoad +noclrinstanceid:CLRMethod:::MethodLoad +nostack:CLRMethod:::MethodLoad +nostack:CLRMethod:::MethodLoad_V1 +nostack:CLRMethod:::MethodLoad_V2 +nomac:CLRMethod:::MethodUnload +noclrinstanceid:CLRMethod:::MethodUnload +nostack:CLRMethod:::MethodUnload +nostack:CLRMethod:::MethodUnload_V1 +nostack:CLRMethod:::MethodUnload_V2 +nomac:CLRMethod:::MethodLoadVerbose +noclrinstanceid:CLRMethod:::MethodLoadVerbose +nostack:CLRMethod:::MethodLoadVerbose +nostack:CLRMethod:::MethodLoadVerbose_V1 +nostack:CLRMethod:::MethodLoadVerbose_V2 +nomac:CLRMethod:::MethodUnloadVerbose +nostack:CLRMethod:::MethodUnloadVerbose +nostack:CLRMethod:::MethodUnloadVerbose_V1 +nostack:CLRMethod:::MethodUnloadVerbose_V2 +noclrinstanceid:CLRMethod:::MethodUnloadVerbose +nomac:CLRMethod:::MethodJittingStarted +noclrinstanceid:CLRMethod:::MethodJittingStarted +nomac:CLRMethod:::MethodJitInliningSucceeded +nostack:CLRMethod:::MethodJitInliningSucceeded +nomac:CLRMethod:::MethodJitInliningFailed +nostack:CLRMethod:::MethodJitInliningFailed +nostack:CLRMethod:::MethodJitTailCallSucceeded +nostack:CLRMethod:::MethodJitTailCallFailed +noclrinstanceid:CLRMethod:::MethodDCStartV2 +noclrinstanceid:CLRMethod:::MethodDCEndV2 +noclrinstanceid:CLRMethod:::MethodDCStartVerboseV2 +noclrinstanceid:CLRMethod:::MethodDCEndVerboseV2 +noclrinstanceid:CLRMethod:::DCStartCompleteV2 +noclrinstanceid:CLRMethod:::DCEndCompleteV2 +nomac:CLRMethod:::MethodILToNativeMap + +############### +# Loader events +############### +nomac:CLRLoader:::ModuleLoad +noclrinstanceid:CLRLoader:::ModuleLoad +nomac:CLRLoader:::ModuleUnload +noclrinstanceid:CLRLoader:::ModuleUnload +nomac:CLRLoader:::AssemblyLoad +noclrinstanceid:CLRLoader:::AssemblyLoad +nomac:CLRLoader:::AssemblyUnload +noclrinstanceid:CLRLoader:::AssemblyUnload +nomac:CLRLoader:::AppDomainLoad +noclrinstanceid:CLRLoader:::AppDomainLoad +nomac:CLRLoader:::AppDomainUnload +noclrinstanceid:CLRLoader:::AppDomainUnload +nomac:CLRLoader:::DomainModuleLoad +noclrinstanceid:CLRLoader:::DomainModuleLoad +noclrinstanceid:CLRLoader:::ModuleDCStartV2 +noclrinstanceid:CLRLoader:::ModuleDCEndV2 +nomac:CLRPerfTrack:::ModuleRangeLoad +nostack:CLRPerfTrack:::ModuleRangeLoad +nomac:CLRLoader:::ModuleLoad_V2 +nomac:CLRLoader:::ModuleUnload_V2 +nomac:CLRLoaderRundown:::ModuleDCStart_V2 +nomac:CLRLoaderRundown:::ModuleDCEnd_V2 + +################# +# Security events +################# +nomac:CLRStrongNameVerification:::StrongNameVerificationStart +noclrinstanceid:CLRStrongNameVerification:::StrongNameVerificationStart +nostack:CLRStrongNameVerification:::StrongNameVerificationStart +nostack:CLRStrongNameVerification:::StrongNameVerificationStart_V1 +nomac:CLRStrongNameVerification:::StrongNameVerificationStart_V1 +nomac:CLRStrongNameVerification:::StrongNameVerificationStop +noclrinstanceid:CLRStrongNameVerification:::StrongNameVerificationStop +nomac:CLRStrongNameVerification:::StrongNameVerificationStop_V1 +nomac:CLRAuthenticodeVerification:::AuthenticodeVerificationStart +noclrinstanceid:CLRAuthenticodeVerification:::AuthenticodeVerificationStart +nostack:CLRAuthenticodeVerification:::AuthenticodeVerificationStart +nomac:CLRAuthenticodeVerification:::AuthenticodeVerificationStart_V1 +nostack:CLRAuthenticodeVerification:::AuthenticodeVerificationStart_V1 +nomac:CLRAuthenticodeVerification:::AuthenticodeVerificationStop +noclrinstanceid:CLRAuthenticodeVerification:::AuthenticodeVerificationStop +nomac:CLRAuthenticodeVerification:::AuthenticodeVerificationStop_V1 + +#################### +# RuntimeInfo events +#################### +nostack:CLRRuntimeInformation:::RuntimeInformationStart + +################################## +# Events from the rundown provider +################################## +nostack::Microsoft-Windows-DotNETRuntimeRundown:: + +################## +# StackWalk events +################## +nomac:CLRStackRundown:::CLRStackWalkDCStart + +############### +# Method events +############### +nomac:CLRMethodRundown:::MethodDCStart +noclrinstanceid:CLRMethodRundown:::MethodDCStart +nomac:CLRMethodRundown:::MethodDCStart_V1 +nomac:CLRMethodRundown:::MethodDCEnd +noclrinstanceid:CLRMethodRundown:::MethodDCEnd +nomac:CLRMethodRundown:::MethodDCEnd_V1 +nomac:CLRMethodRundown:::MethodDCStartVerbose +noclrinstanceid:CLRMethodRundown:::MethodDCStartVerbose +nomac:CLRMethodRundown:::MethodDCStartVerbose_V1 +nomac:CLRMethodRundown:::MethodDCEndVerbose +noclrinstanceid:CLRMethodRundown:::MethodDCEndVerbose +nomac:CLRMethodRundown:::MethodDCEndVerbose_V1 +nomac:CLRMethodRundown:::DCStartComplete +noclrinstanceid:CLRMethodRundown:::DCStartComplete +nomac:CLRMethodRundown:::DCStartComplete_V1 +nomac:CLRMethodRundown:::DCEndComplete +noclrinstanceid:CLRMethodRundown:::DCEndComplete +nomac:CLRMethodRundown:::DCEndComplete_V1 +nomac:CLRMethodRundown:::DCStartInit +noclrinstanceid:CLRMethodRundown:::DCStartInit +nomac:CLRMethodRundown:::DCStartInit_V1 +nomac:nomac:CLRMethodRundown:::DCEndInit +noclrinstanceid:nomac:CLRMethodRundown:::DCEndInit +nomac:CLRMethodRundown:::DCEndInit_V1 +nomac:CLRMethodRundown:::MethodDCStartILToNativeMap +nomac:CLRMethodRundown:::MethodDCEndILToNativeMap + +############### +# Loader events +############### +nomac:CLRLoaderRundown:::DomainModuleDCStart +noclrinstanceid:CLRLoaderRundown:::DomainModuleDCStart +nomac:CLRLoaderRundown:::DomainModuleDCStart_V1 +nomac:CLRLoaderRundown:::DomainModuleDCEnd +noclrinstanceid:CLRLoaderRundown:::DomainModuleDCEnd +nomac:CLRLoaderRundown:::DomainModuleDCEnd_V1 +nomac:CLRLoaderRundown:::ModuleDCStart +noclrinstanceid:CLRLoaderRundown:::ModuleDCStart +nomac:CLRLoaderRundown:::ModuleDCStart_V1 +nomac:CLRLoaderRundown:::ModuleDCEnd +noclrinstanceid:CLRLoaderRundown:::ModuleDCEnd +nomac:CLRLoaderRundown:::ModuleDCEnd_V1 +nomac:CLRLoaderRundown:::AssemblyDCStart +noclrinstanceid:CLRLoaderRundown:::AssemblyDCStart +nomac:CLRLoaderRundown:::AssemblyDCStart_V1 +nomac:CLRLoaderRundown:::AssemblyDCEnd +noclrinstanceid:CLRLoaderRundown:::AssemblyDCEnd +nomac:CLRLoaderRundown:::AssemblyDCEnd_V1 +nomac:CLRLoaderRundown:::AppDomainDCStart +noclrinstanceid:CLRLoaderRundown:::AppDomainDCStart +nomac:CLRLoaderRundown:::AppDomainDCStart_V1 +nomac:CLRLoaderRundown:::AppDomainDCEnd +noclrinstanceid:CLRLoaderRundown:::AppDomainDCEnd +nomac:CLRLoaderRundown:::AppDomainDCEnd_V1 +nomac:CLRLoaderRundown:::ThreadDC +nomac:CLRPerfTrack:::ModuleRangeDCStart +nostack:CLRPerfTrack:::ModuleRangeDCStart +nomac:CLRPerfTrack:::ModuleRangeDCEnd +nostack:CLRPerfTrack:::ModuleRangeDCEnd + +#################### +# RuntimeInfo events +#################### +nomac:CLRRuntimeInformationRundown:::RuntimeInformationDCStart + +################################## +# Events from the private provider +################################## +nostack::Microsoft-Windows-DotNETRuntimePrivate:: + +########################## +# GarbageCollection events +########################## +nomac:GarbageCollectionPrivate:::GCDecision +noclrinstanceid:GarbageCollectionPrivate:::GCDecision +nomac:GarbageCollectionPrivate:::GCDecision_V1 +nomac:GarbageCollectionPrivate:::GCSettings +noclrinstanceid:GarbageCollectionPrivate:::GCSettings +nomac:GarbageCollectionPrivate:::GCSettings_V1 +nomac:GarbageCollectionPrivate:::GCOptimized +noclrinstanceid:GarbageCollectionPrivate:::GCOptimized +nomac:GarbageCollectionPrivate:::GCPerHeapHistory +noclrinstanceid:GarbageCollectionPrivate:::GCPerHeapHistory +nomac:GarbageCollectionPrivate:::GCPerHeapHistory_V1 +nomac:GarbageCollectionPrivate:::GCGlobalHeapHistory +noclrinstanceid:GarbageCollectionPrivate:::GCGlobalHeapHistory +nomac:GarbageCollectionPrivate:::GCGlobalHeapHistory_V1 +nomac:GarbageCollectionPrivate:::GCJoin +noclrinstanceid:GarbageCollectionPrivate:::GCJoin +nomac:GarbageCollectionPrivate:::GCJoin_V1 +nomac:GarbageCollectionPrivate:::PrvGCMarkStackRoots +noclrinstanceid:GarbageCollectionPrivate:::PrvGCMarkStackRoots +nomac:GarbageCollectionPrivate:::PrvGCMarkStackRoots_V1 +nomac:GarbageCollectionPrivate:::PrvGCMarkFinalizeQueueRoots +noclrinstanceid:GarbageCollectionPrivate:::PrvGCMarkFinalizeQueueRoots +nomac:GarbageCollectionPrivate:::PrvGCMarkFinalizeQueueRoots_V1 +nomac:GarbageCollectionPrivate:::PrvGCMarkHandles +noclrinstanceid:GarbageCollectionPrivate:::PrvGCMarkHandles +nomac:GarbageCollectionPrivate:::PrvGCMarkHandles_V1 +nomac:GarbageCollectionPrivate:::PrvGCMarkCards +noclrinstanceid:GarbageCollectionPrivate:::PrvGCMarkCards +nomac:GarbageCollectionPrivate:::PrvGCMarkCards_V1 +nomac:GarbageCollectionPrivate:::BGCBegin +nomac:GarbageCollectionPrivate:::BGC1stNonConEnd +nomac:GarbageCollectionPrivate:::BGC1stConEnd +nomac:GarbageCollectionPrivate:::BGC2ndNonConBegin +nomac:GarbageCollectionPrivate:::BGC2ndNonConEnd +nomac:GarbageCollectionPrivate:::BGC2ndConBegin +nomac:GarbageCollectionPrivate:::BGC2ndConEnd +nomac:GarbageCollectionPrivate:::BGCPlanEnd +nomac:GarbageCollectionPrivate:::BGCSweepEnd +nomac:GarbageCollectionPrivate:::BGCDrainMark +nomac:GarbageCollectionPrivate:::BGCRevisit +nomac:GarbageCollectionPrivate:::BGCOverflow +nomac:GarbageCollectionPrivate:::BGCAllocWaitBegin +nomac:GarbageCollectionPrivate:::BGCAllocWaitEnd +nomac:GarbageCollectionPrivate:::GCFullNotify +noclrinstanceid:GarbageCollectionPrivate:::GCFullNotify +stack:GarbageCollectionPrivate:Microsoft-Windows-DotNETRuntimePrivate::SetGCHandle +stack:GarbageCollectionPrivate:Microsoft-Windows-DotNETRuntimePrivate::DestroyGCHandle +stack:GarbageCollectionPrivate:Microsoft-Windows-DotNETRuntimePrivate::CCWRefCountChange + +################ +# Startup events +################ +nomac:Startup:::EEStartupStart +noclrinstanceid:Startup:::EEStartupStart +nomac:Startup:::EEStartupEnd +noclrinstanceid:Startup:::EEStartupEnd +nomac:Startup:::EEStartupEnd_V1 +nomac:Startup:::EEConfigSetup +noclrinstanceid:Startup:::EEConfigSetup +nomac:Startup:::EEConfigSetupEnd +noclrinstanceid:Startup:::EEConfigSetupEnd +nomac:Startup:::LdSysBases +noclrinstanceid:Startup:::LdSysBases +nomac:Startup:::LdSysBasesEnd +noclrinstanceid:Startup:::LdSysBasesEnd +nomac:Startup:::ExecExe +noclrinstanceid:Startup:::ExecExe +nomac:Startup:::ExecExe_V1 +nomac:Startup:::ExecExeEnd +noclrinstanceid:Startup:::ExecExeEnd +nomac:Startup:::ExecExeEnd_V1 +nomac:Startup:::Main +noclrinstanceid:Startup:::Main +nomac:Startup:::MainEnd +noclrinstanceid:Startup:::MainEnd +nomac:Startup:::ApplyPolicyStart +noclrinstanceid:Startup:::ApplyPolicyStart +nomac:Startup:::ApplyPolicyStart_V1 +nomac:Startup:::ApplyPolicyEnd +noclrinstanceid:Startup:::ApplyPolicyEnd +nomac:Startup:::ApplyPolicyEnd_V1 +nomac:Startup:::LdLibShFolder +noclrinstanceid:Startup:::LdLibShFolder +nomac:Startup:::LdLibShFolder_V1 +nomac:Startup:::LdLibShFolderEnd +noclrinstanceid:Startup:::LdLibShFolderEnd +nomac:Startup:::LdLibShFolderEnd_V1 +nomac:Startup:::PrestubWorker +noclrinstanceid:Startup:::PrestubWorker +nomac:Startup:::PrestubWorkerEnd +noclrinstanceid:Startup:::PrestubWorkerEnd +nomac:Startup:::PrestubWorkerEnd_V1 +nomac:Startup:::GetInstallationStart +noclrinstanceid:Startup:::GetInstallationStart +nomac:Startup:::GetInstallationStart_V1 +nomac:Startup:::GetInstallationEnd +noclrinstanceid:Startup:::GetInstallationEnd +nomac:Startup:::GetInstallationEnd_V1 +nomac:Startup:::OpenHModule +noclrinstanceid:Startup:::OpenHModule +nomac:Startup:::OpenHModule_V1 +nomac:Startup:::OpenHModuleEnd +noclrinstanceid:Startup:::OpenHModuleEnd +nomac:Startup:::OpenHModuleEnd_V1 +nomac:Startup:::ExplicitBindStart +noclrinstanceid:Startup:::ExplicitBindStart +nomac:Startup:::ExplicitBindStart_V1 +nomac:Startup:::ExplicitBindEnd +noclrinstanceid:Startup:::ExplicitBindEnd +nomac:Startup:::ExplicitBindEnd_V1 +nomac:Startup:::ParseXml +noclrinstanceid:Startup:::ParseXml +nomac:Startup:::ParseXml_V1 +nomac:Startup:::ParseXmlEnd +noclrinstanceid:Startup:::ParseXmlEnd +nomac:Startup:::ParseXmlEnd_V1 +nomac:Startup:::InitDefaultDomain +noclrinstanceid:Startup:::InitDefaultDomain +nomac:Startup:::InitDefaultDomainEnd +noclrinstanceid:Startup:::InitDefaultDomainEnd +nomac:Startup:::InitSecurity +noclrinstanceid:Startup:::InitSecurity +nomac:Startup:::InitSecurity_V1 +nomac:Startup:::InitSecurityEnd +noclrinstanceid:Startup:::InitSecurityEnd +nomac:Startup:::InitSecurityEnd_V1 +nomac:Startup:::AllowBindingRedirs +noclrinstanceid:Startup:::AllowBindingRedirs +nomac:Startup:::AllowBindingRedirs_V1 +nomac:Startup:::AllowBindingRedirsEnd +noclrinstanceid:Startup:::AllowBindingRedirsEnd +nomac:Startup:::AllowBindingRedirsEnd_V1 +nomac:Startup:::EEConfigSync +noclrinstanceid:Startup:::EEConfigSync +nomac:Startup:::EEConfigSyncEnd +noclrinstanceid:Startup:::EEConfigSyncEnd +nomac:Startup:::FusionBinding +noclrinstanceid:Startup:::FusionBinding +nomac:Startup:::FusionBindingEnd +noclrinstanceid:Startup:::FusionBindingEnd +nomac:Startup:::LoaderCatchCall +noclrinstanceid:Startup:::LoaderCatchCall +nomac:Startup:::LoaderCatchCallEnd +noclrinstanceid:Startup:::LoaderCatchCallEnd +nomac:Startup:::FusionInit +noclrinstanceid:Startup:::FusionInit +nomac:Startup:::FusionInit_V1 +nomac:Startup:::FusionInitEnd +noclrinstanceid:Startup:::FusionInitEnd +nomac:Startup:::FusionInitEnd_V1 +nomac:Startup:::FusionAppCtx +noclrinstanceid:Startup:::FusionAppCtx +nomac:Startup:::FusionAppCtxEnd +noclrinstanceid:Startup:::FusionAppCtxEnd +nomac:Startup:::Fusion2EE +noclrinstanceid:Startup:::Fusion2EE +nomac:Startup:::Fusion2EE_V1 +nomac:Startup:::Fusion2EEEnd +noclrinstanceid:Startup:::Fusion2EEEnd +nomac:Startup:::Fusion2EEEnd_V1 +nomac:Startup:::SecurityCatchCall +noclrinstanceid:Startup:::SecurityCatchCall +nomac:Startup:::SecurityCatchCallEnd +noclrinstanceid:Startup:::SecurityCatchCallEnd + +################## +# Loader events +################## +stack:LoaderHeapAllocation:Microsoft-Windows-DotNETRuntimePrivate::AllocRequest + +################## +# StackWalk events +################## +nomac:CLRStackPrivate:::CLRStackWalkPrivate + +################ +# Binding events +################ +nomac:Binding:::BindingPolicyPhaseStart +nomac:Binding:::BindingPolicyPhaseEnd +nomac:Binding:::BindingNgenPhaseStart +nomac:Binding:::BindingNgenPhaseEnd +nomac:Binding:::BindingLookupAndProbingPhaseStart +nomac:Binding:::BindingLookupAndProbingPhaseEnd +nomac:Binding:::LoaderPhaseStart +nomac:Binding:::LoaderPhaseEnd +nomac:Binding:::BindingPhaseStart +nomac:Binding:::BindingPhaseEnd +nomac:Binding:::BindingDownloadPhaseStart +nomac:Binding:::BindingDownloadPhaseEnd +nomac:Binding:::LoaderAssemblyInitPhaseStart +nomac:Binding:::LoaderAssemblyInitPhaseEnd +nomac:Binding:::LoaderMappingPhaseStart +nomac:Binding:::LoaderMappingPhaseEnd +nomac:Binding:::LoaderDeliverEventsPhaseStart +nomac:Binding:::LoaderDeliverEventsPhaseEnd +nomac:Binding:::EvidenceGenerated +nomac:Binding:::FusionMessage +stack:Binding:::FusionMessage +nomac:Binding:::FusionErrorCode +stack:Binding:::FusionErrorCode + +################ +# ModuleRange event +################ +nomac:CLRPerfTrackPrivate:::ModuleRangeLoadPrivate +nostack:CLRPerfTrackPrivate:::ModuleRangeLoadPrivate + +################################# +# Events from the stress provider +################################# +nostack::Microsoft-Windows-DotNETRuntimeStress:: + +################## +# StressLog events +################## +nomac:StressLogTask:::StressLogEvent +noclrinstanceid:StressLogTask:::StressLogEvent +nomac:StressLogTask:::StressLogEvent_V1 + +################## +# StackWalk events +################## +nomac:CLRStackStress:::CLRStackWalkStress diff --git a/src/vm/amd64/.gitmirror b/src/vm/amd64/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/vm/amd64/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file diff --git a/src/vm/amd64/AsmHelpers.asm b/src/vm/amd64/AsmHelpers.asm new file mode 100644 index 0000000000..4563a060b3 --- /dev/null +++ b/src/vm/amd64/AsmHelpers.asm @@ -0,0 +1,764 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; ==++== +; + +; +; ==--== +; +; FILE: asmhelpers.asm +; + +; +; ====================================================================================== + +include AsmMacros.inc +include asmconstants.inc + +extern JIT_InternalThrow:proc +extern NDirectImportWorker:proc +extern ThePreStub:proc +extern ProfileEnter:proc +extern ProfileLeave:proc +extern ProfileTailcall:proc +extern OnHijackWorker:proc +extern JIT_RareDisableHelperWorker:proc + +ifdef _DEBUG +extern DebugCheckStubUnwindInfoWorker:proc +endif + + +GenerateArrayOpStubExceptionCase macro ErrorCaseName, ExceptionName + +NESTED_ENTRY ErrorCaseName&_RSIRDI_ScratchArea, _TEXT + + ; account for scratch area, rsi, rdi already on the stack + .allocstack 38h + END_PROLOGUE + + mov rcx, CORINFO_&ExceptionName&_ASM + + ; begin epilogue + + add rsp, 28h ; pop callee scratch area + pop rdi + pop rsi + jmp JIT_InternalThrow + +NESTED_END ErrorCaseName&_RSIRDI_ScratchArea, _TEXT + +NESTED_ENTRY ErrorCaseName&_ScratchArea, _TEXT + + ; account for scratch area already on the stack + .allocstack 28h + END_PROLOGUE + + mov rcx, CORINFO_&ExceptionName&_ASM + + ; begin epilogue + + add rsp, 28h ; pop callee scratch area + jmp JIT_InternalThrow + +NESTED_END ErrorCaseName&_ScratchArea, _TEXT + +NESTED_ENTRY ErrorCaseName&_RSIRDI, _TEXT + + ; account for rsi, rdi already on the stack + .allocstack 10h + END_PROLOGUE + + mov rcx, CORINFO_&ExceptionName&_ASM + + ; begin epilogue + + pop rdi + pop rsi + jmp JIT_InternalThrow + +NESTED_END ErrorCaseName&_RSIRDI, _TEXT + +LEAF_ENTRY ErrorCaseName, _TEXT + + mov rcx, CORINFO_&ExceptionName&_ASM + + ; begin epilogue + + jmp JIT_InternalThrow + +LEAF_END ErrorCaseName, _TEXT + + endm + + +GenerateArrayOpStubExceptionCase ArrayOpStubNullException, NullReferenceException +GenerateArrayOpStubExceptionCase ArrayOpStubRangeException, IndexOutOfRangeException +GenerateArrayOpStubExceptionCase ArrayOpStubTypeMismatchException, ArrayTypeMismatchException + + +; EXTERN_C int __fastcall HelperMethodFrameRestoreState( +; INDEBUG_COMMA(HelperMethodFrame *pFrame) +; MachState *pState +; ) +LEAF_ENTRY HelperMethodFrameRestoreState, _TEXT + +ifdef _DEBUG + mov rcx, rdx +endif + + ; Check if the MachState is valid + xor eax, eax + cmp qword ptr [rcx + OFFSETOF__MachState___pRetAddr], rax + jne @F + REPRET +@@: + + ; + ; If a preserved register were pushed onto the stack between + ; the managed caller and the H_M_F, m_pReg will point to its + ; location on the stack and it would have been updated on the + ; stack by the GC already and it will be popped back into the + ; appropriate register when the appropriate epilog is run. + ; + ; Otherwise, the register is preserved across all the code + ; in this HCALL or FCALL, so we need to update those registers + ; here because the GC will have updated our copies in the + ; frame. + ; + ; So, if m_pReg points into the MachState, we need to update + ; the register here. That's what this macro does. + ; +RestoreReg macro reg, regnum + lea rax, [rcx + OFFSETOF__MachState__m_Capture + 8 * regnum] + mov rdx, [rcx + OFFSETOF__MachState__m_Ptrs + 8 * regnum] + cmp rax, rdx + cmove reg, [rax] + endm + + ; regnum has to match ENUM_CALLEE_SAVED_REGISTERS macro + RestoreReg Rdi, 0 + RestoreReg Rsi, 1 + RestoreReg Rbx, 2 + RestoreReg Rbp, 3 + RestoreReg R12, 4 + RestoreReg R13, 5 + RestoreReg R14, 6 + RestoreReg R15, 7 + + xor eax, eax + ret + +LEAF_END HelperMethodFrameRestoreState, _TEXT + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; NDirectImportThunk +;; +;; In addition to being called by the EE, this function can be called +;; directly from code generated by JIT64 for CRT optimized direct +;; P/Invoke calls. If it is modified, the JIT64 compiler's code +;; generation will need to altered accordingly. +;; +; EXTERN_C VOID __stdcall NDirectImportThunk(); +NESTED_ENTRY NDirectImportThunk, _TEXT + + ; + ; Allocate space for XMM parameter registers and callee scratch area. + ; + alloc_stack 68h + + ; + ; Save integer parameter registers. + ; Make sure to preserve r11 as well as it is used to pass the stack argument size from JIT + ; + save_reg_postrsp rcx, 70h + save_reg_postrsp rdx, 78h + save_reg_postrsp r8, 80h + save_reg_postrsp r9, 88h + save_reg_postrsp r11, 60h + + save_xmm128_postrsp xmm0, 20h + save_xmm128_postrsp xmm1, 30h + save_xmm128_postrsp xmm2, 40h + save_xmm128_postrsp xmm3, 50h + END_PROLOGUE + + ; + ; Call NDirectImportWorker w/ the NDirectMethodDesc* + ; + mov rcx, METHODDESC_REGISTER + call NDirectImportWorker + + ; + ; Restore parameter registers + ; + mov rcx, [rsp + 70h] + mov rdx, [rsp + 78h] + mov r8, [rsp + 80h] + mov r9, [rsp + 88h] + mov r11, [rsp + 60h] + movdqa xmm0, [rsp + 20h] + movdqa xmm1, [rsp + 30h] + movdqa xmm2, [rsp + 40h] + movdqa xmm3, [rsp + 50h] + + ; + ; epilogue, rax contains the native target address + ; + add rsp, 68h + + TAILJMP_RAX +NESTED_END NDirectImportThunk, _TEXT + + +;------------------------------------------------ +; JIT_RareDisableHelper +; +; The JIT expects this helper to preserve all +; registers that can be used for return values +; + +NESTED_ENTRY JIT_RareDisableHelper, _TEXT + + alloc_stack 38h + END_PROLOGUE + + movdqa [rsp+20h], xmm0 ; Save xmm0 + mov [rsp+30h], rax ; Save rax + + call JIT_RareDisableHelperWorker + + movdqa xmm0, [rsp+20h] ; Restore xmm0 + mov rax, [rsp+30h] ; Restore rax + + add rsp, 38h + ret + +NESTED_END JIT_RareDisableHelper, _TEXT + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; PrecodeFixupThunk +;; +;; The call in fixup precode initally points to this function. +;; The pupose of this function is to load the MethodDesc and forward the call the prestub. +;; +; EXTERN_C VOID __stdcall PrecodeFixupThunk(); +LEAF_ENTRY PrecodeFixupThunk, _TEXT + + pop rax ; Pop the return address. It points right after the call instruction in the precode. + + ; Inline computation done by FixupPrecode::GetMethodDesc() + movzx r10,byte ptr [rax+2] ; m_PrecodeChunkIndex + movzx r11,byte ptr [rax+1] ; m_MethodDescChunkIndex + mov rax,qword ptr [rax+r10*8+3] + lea METHODDESC_REGISTER,[rax+r11*8] + + ; Tail call to prestub + jmp ThePreStub + +LEAF_END PrecodeFixupThunk, _TEXT + + +; extern "C" void setFPReturn(int fpSize, INT64 retVal); +LEAF_ENTRY setFPReturn, _TEXT + cmp ecx, 4 + je setFPReturn4 + cmp ecx, 8 + jne setFPReturnNot8 + mov [rsp+10h], rdx + movsd xmm0, real8 ptr [rsp+10h] +setFPReturnNot8: + REPRET + +setFPReturn4: + mov [rsp+10h], rdx + movss xmm0, real4 ptr [rsp+10h] + ret +LEAF_END setFPReturn, _TEXT + + +; extern "C" void getFPReturn(int fpSize, INT64 *retval); +LEAF_ENTRY getFPReturn, _TEXT + cmp ecx, 4 + je getFPReturn4 + cmp ecx, 8 + jne getFPReturnNot8 + movsd real8 ptr [rdx], xmm0 +getFPReturnNot8: + REPRET + +getFPReturn4: + movss real4 ptr [rdx], xmm0 + ret +LEAF_END getFPReturn, _TEXT + + +ifdef _DEBUG +NESTED_ENTRY DebugCheckStubUnwindInfo, _TEXT + + ; + ; rax is pushed on the stack before being trashed by the "mov rax, + ; target/jmp rax" code generated by X86EmitNearJump. This stack slot + ; will be reused later in the epilogue. This slot is left there to + ; align rsp. + ; + + .allocstack 8 + + mov rax, [rsp] + + ; + ; Create a CONTEXT structure. DebugCheckStubUnwindInfoWorker will + ; fill in the flags. + ; + + alloc_stack 20h + SIZEOF__CONTEXT + + mov r10, rbp + + set_frame rbp, 20h + + mov [rbp + OFFSETOF__CONTEXT__Rbp], r10 + .savereg rbp, OFFSETOF__CONTEXT__Rbp + + save_reg_frame rbx, rbp, OFFSETOF__CONTEXT__Rbx + save_reg_frame rsi, rbp, OFFSETOF__CONTEXT__Rsi + save_reg_frame rdi, rbp, OFFSETOF__CONTEXT__Rdi + save_reg_frame r12, rbp, OFFSETOF__CONTEXT__R12 + save_reg_frame r13, rbp, OFFSETOF__CONTEXT__R13 + save_reg_frame r14, rbp, OFFSETOF__CONTEXT__R14 + save_reg_frame r15, rbp, OFFSETOF__CONTEXT__R15 + save_xmm128_frame xmm6, rbp, OFFSETOF__CONTEXT__Xmm6 + save_xmm128_frame xmm7, rbp, OFFSETOF__CONTEXT__Xmm7 + save_xmm128_frame xmm8, rbp, OFFSETOF__CONTEXT__Xmm8 + save_xmm128_frame xmm9, rbp, OFFSETOF__CONTEXT__Xmm9 + save_xmm128_frame xmm10, rbp, OFFSETOF__CONTEXT__Xmm10 + save_xmm128_frame xmm11, rbp, OFFSETOF__CONTEXT__Xmm11 + save_xmm128_frame xmm12, rbp, OFFSETOF__CONTEXT__Xmm12 + save_xmm128_frame xmm13, rbp, OFFSETOF__CONTEXT__Xmm13 + save_xmm128_frame xmm14, rbp, OFFSETOF__CONTEXT__Xmm14 + save_xmm128_frame xmm15, rbp, OFFSETOF__CONTEXT__Xmm15 + END_PROLOGUE + + mov [rbp + OFFSETOF__CONTEXT__Rax], rax + mov [rbp + OFFSETOF__CONTEXT__Rcx], rcx + mov [rbp + OFFSETOF__CONTEXT__Rdx], rdx + mov [rbp + OFFSETOF__CONTEXT__R8], r8 + mov [rbp + OFFSETOF__CONTEXT__R9], r9 + mov [rbp + OFFSETOF__CONTEXT__R10], r10 + mov [rbp + OFFSETOF__CONTEXT__R11], r11 + movdqa [rbp + OFFSETOF__CONTEXT__Xmm0], xmm0 + movdqa [rbp + OFFSETOF__CONTEXT__Xmm1], xmm1 + movdqa [rbp + OFFSETOF__CONTEXT__Xmm2], xmm2 + movdqa [rbp + OFFSETOF__CONTEXT__Xmm3], xmm3 + movdqa [rbp + OFFSETOF__CONTEXT__Xmm4], xmm4 + movdqa [rbp + OFFSETOF__CONTEXT__Xmm5], xmm5 + + mov rax, [rbp+SIZEOF__CONTEXT+8] + mov [rbp+OFFSETOF__CONTEXT__Rip], rax + + lea rax, [rbp+SIZEOF__CONTEXT+8+8] + mov [rbp+OFFSETOF__CONTEXT__Rsp], rax + + ; + ; Align rsp + ; + and rsp, -16 + + ; + ; Verify that unwinding works from the stub's CONTEXT. + ; + + mov rcx, rbp + call DebugCheckStubUnwindInfoWorker + + ; + ; Restore stub's registers. rbp will be restored using "pop" in the + ; epilogue. + ; + + mov rax, [rbp+OFFSETOF__CONTEXT__Rbp] + mov [rbp+SIZEOF__CONTEXT], rax + + mov rax, [rbp+OFFSETOF__CONTEXT__Rax] + mov rbx, [rbp+OFFSETOF__CONTEXT__Rbx] + mov rcx, [rbp+OFFSETOF__CONTEXT__Rcx] + mov rdx, [rbp+OFFSETOF__CONTEXT__Rdx] + mov rsi, [rbp+OFFSETOF__CONTEXT__Rsi] + mov rdi, [rbp+OFFSETOF__CONTEXT__Rdi] + mov r8, [rbp+OFFSETOF__CONTEXT__R8] + mov r9, [rbp+OFFSETOF__CONTEXT__R9] + mov r10, [rbp+OFFSETOF__CONTEXT__R10] + mov r11, [rbp+OFFSETOF__CONTEXT__R11] + mov r12, [rbp+OFFSETOF__CONTEXT__R12] + mov r13, [rbp+OFFSETOF__CONTEXT__R13] + mov r14, [rbp+OFFSETOF__CONTEXT__R14] + mov r15, [rbp+OFFSETOF__CONTEXT__R15] + movdqa xmm0, [rbp+OFFSETOF__CONTEXT__Xmm0] + movdqa xmm1, [rbp+OFFSETOF__CONTEXT__Xmm1] + movdqa xmm2, [rbp+OFFSETOF__CONTEXT__Xmm2] + movdqa xmm3, [rbp+OFFSETOF__CONTEXT__Xmm3] + movdqa xmm4, [rbp+OFFSETOF__CONTEXT__Xmm4] + movdqa xmm5, [rbp+OFFSETOF__CONTEXT__Xmm5] + movdqa xmm6, [rbp+OFFSETOF__CONTEXT__Xmm6] + movdqa xmm7, [rbp+OFFSETOF__CONTEXT__Xmm7] + movdqa xmm8, [rbp+OFFSETOF__CONTEXT__Xmm8] + movdqa xmm9, [rbp+OFFSETOF__CONTEXT__Xmm9] + movdqa xmm10, [rbp+OFFSETOF__CONTEXT__Xmm10] + movdqa xmm11, [rbp+OFFSETOF__CONTEXT__Xmm11] + movdqa xmm12, [rbp+OFFSETOF__CONTEXT__Xmm12] + movdqa xmm13, [rbp+OFFSETOF__CONTEXT__Xmm13] + movdqa xmm14, [rbp+OFFSETOF__CONTEXT__Xmm14] + movdqa xmm15, [rbp+OFFSETOF__CONTEXT__Xmm15] + + ; + ; epilogue + ; + + lea rsp, [rbp + SIZEOF__CONTEXT] + pop rbp + ret + +NESTED_END DebugCheckStubUnwindInfo, _TEXT +endif ; _DEBUG + + +; A JITted method's return address was hijacked to return to us here. +; VOID OnHijackTripThread() +NESTED_ENTRY OnHijackTripThread, _TEXT + + ; Don't fiddle with this unless you change HijackFrame::UpdateRegDisplay + ; and HijackObjectArgs + push rax ; make room for the real return address (Rip) + PUSH_CALLEE_SAVED_REGISTERS + push_vol_reg rax + mov rcx, rsp + + alloc_stack 30h ; make extra room for xmm0 + save_xmm128_postrsp xmm0, 20h + + + END_PROLOGUE + + call OnHijackWorker + + movdqa xmm0, [rsp + 20h] + + add rsp, 30h + pop rax + POP_CALLEE_SAVED_REGISTERS + ret ; return to the correct place, adjusted by our caller +NESTED_END OnHijackTripThread, _TEXT + + +; +; typedef struct _PROFILE_PLATFORM_SPECIFIC_DATA +; { +; FunctionID *functionId; // function ID comes in the r11 register +; void *rbp; +; void *probersp; +; void *ip; +; void *profiledRsp; +; UINT64 rax; +; LPVOID hiddenArg; +; UINT64 flt0; +; UINT64 flt1; +; UINT64 flt2; +; UINT64 flt3; +; UINT32 flags; +; } PROFILE_PLATFORM_SPECIFIC_DATA, *PPROFILE_PLATFORM_SPECIFIC_DATA; +; +SIZEOF_PROFILE_PLATFORM_SPECIFIC_DATA equ 8h*11 + 4h*2 ; includes fudge to make FP_SPILL right +SIZEOF_OUTGOING_ARGUMENT_HOMES equ 8h*4 +SIZEOF_FP_ARG_SPILL equ 10h*1 + +; Need to be careful to keep the stack 16byte aligned here, since we are pushing 3 +; arguments that will align the stack and we just want to keep it aligned with our +; SIZEOF_STACK_FRAME + +OFFSETOF_PLATFORM_SPECIFIC_DATA equ SIZEOF_OUTGOING_ARGUMENT_HOMES + +; we'll just spill into the PROFILE_PLATFORM_SPECIFIC_DATA structure +OFFSETOF_FP_ARG_SPILL equ SIZEOF_OUTGOING_ARGUMENT_HOMES + \ + SIZEOF_PROFILE_PLATFORM_SPECIFIC_DATA + +SIZEOF_STACK_FRAME equ SIZEOF_OUTGOING_ARGUMENT_HOMES + \ + SIZEOF_PROFILE_PLATFORM_SPECIFIC_DATA + \ + SIZEOF_MAX_FP_ARG_SPILL + +PROFILE_ENTER equ 1h +PROFILE_LEAVE equ 2h +PROFILE_TAILCALL equ 4h + +; *********************************************************** +; NOTE: +; +; Register preservation scheme: +; +; Preserved: +; - all non-volatile registers +; - rax +; - xmm0 +; +; Not Preserved: +; - integer argument registers (rcx, rdx, r8, r9) +; - floating point argument registers (xmm1-3) +; - volatile integer registers (r10, r11) +; - volatile floating point registers (xmm4-5) +; +; *********************************************************** + +; void JIT_ProfilerEnterLeaveTailcallStub(UINT_PTR ProfilerHandle) +LEAF_ENTRY JIT_ProfilerEnterLeaveTailcallStub, _TEXT + REPRET +LEAF_END JIT_ProfilerEnterLeaveTailcallStub, _TEXT + +;EXTERN_C void ProfileEnterNaked(FunctionIDOrClientID functionIDOrClientID, size_t profiledRsp); +NESTED_ENTRY ProfileEnterNaked, _TEXT + push_nonvol_reg rax + +; Upon entry : +; rcx = clientInfo +; rdx = profiledRsp + + lea rax, [rsp + 10h] ; caller rsp + mov r10, [rax - 8h] ; return address + + alloc_stack SIZEOF_STACK_FRAME + + ; correctness of return value in structure doesn't matter for enter probe + + + ; setup ProfilePlatformSpecificData structure + xor r8, r8; + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 0h], r8 ; r8 is null -- struct functionId field + save_reg_postrsp rbp, OFFSETOF_PLATFORM_SPECIFIC_DATA + 8h ; -- struct rbp field + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 10h], rax ; caller rsp -- struct probeRsp field + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 18h], r10 ; return address -- struct ip field + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 20h], rdx ; -- struct profiledRsp field + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 28h], r8 ; r8 is null -- struct rax field + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 30h], r8 ; r8 is null -- struct hiddenArg field + movsd real8 ptr [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 38h], xmm0 ; -- struct flt0 field + movsd real8 ptr [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 40h], xmm1 ; -- struct flt1 field + movsd real8 ptr [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 48h], xmm2 ; -- struct flt2 field + movsd real8 ptr [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 50h], xmm3 ; -- struct flt3 field + mov r10, PROFILE_ENTER + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 58h], r10d ; flags ; -- struct flags field + + ; we need to be able to restore the fp return register + save_xmm128_postrsp xmm0, OFFSETOF_FP_ARG_SPILL + 0h + END_PROLOGUE + + ; rcx already contains the clientInfo + lea rdx, [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA] + call ProfileEnter + + ; restore fp return register + movdqa xmm0, [rsp + OFFSETOF_FP_ARG_SPILL + 0h] + + ; begin epilogue + add rsp, SIZEOF_STACK_FRAME + pop rax + ret +NESTED_END ProfileEnterNaked, _TEXT + +;EXTERN_C void ProfileLeaveNaked(FunctionIDOrClientID functionIDOrClientID, size_t profiledRsp); +NESTED_ENTRY ProfileLeaveNaked, _TEXT + push_nonvol_reg rax + +; Upon entry : +; rcx = clientInfo +; rdx = profiledRsp + + ; need to be careful with rax here because it contains the return value which we want to harvest + + lea r10, [rsp + 10h] ; caller rsp + mov r11, [r10 - 8h] ; return address + + alloc_stack SIZEOF_STACK_FRAME + + ; correctness of argument registers in structure doesn't matter for leave probe + + ; setup ProfilePlatformSpecificData structure + xor r8, r8; + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 0h], r8 ; r8 is null -- struct functionId field + save_reg_postrsp rbp, OFFSETOF_PLATFORM_SPECIFIC_DATA + 8h ; -- struct rbp field + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 10h], r10 ; caller rsp -- struct probeRsp field + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 18h], r11 ; return address -- struct ip field + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 20h], rdx ; -- struct profiledRsp field + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 28h], rax ; return value -- struct rax field + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 30h], r8 ; r8 is null -- struct hiddenArg field + movsd real8 ptr [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 38h], xmm0 ; -- struct flt0 field + movsd real8 ptr [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 40h], xmm1 ; -- struct flt1 field + movsd real8 ptr [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 48h], xmm2 ; -- struct flt2 field + movsd real8 ptr [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 50h], xmm3 ; -- struct flt3 field + mov r10, PROFILE_LEAVE + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 58h], r10d ; flags -- struct flags field + + ; we need to be able to restore the fp return register + save_xmm128_postrsp xmm0, OFFSETOF_FP_ARG_SPILL + 0h + END_PROLOGUE + + ; rcx already contains the clientInfo + lea rdx, [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA] + call ProfileLeave + + ; restore fp return register + movdqa xmm0, [rsp + OFFSETOF_FP_ARG_SPILL + 0h] + + ; begin epilogue + add rsp, SIZEOF_STACK_FRAME + pop rax + ret +NESTED_END ProfileLeaveNaked, _TEXT + +;EXTERN_C void ProfileTailcallNaked(FunctionIDOrClientID functionIDOrClientID, size_t profiledRsp); +NESTED_ENTRY ProfileTailcallNaked, _TEXT + push_nonvol_reg rax + +; Upon entry : +; rcx = clientInfo +; rdx = profiledRsp + + lea rax, [rsp + 10h] ; caller rsp + mov r11, [rax - 8h] ; return address + + alloc_stack SIZEOF_STACK_FRAME + + ; correctness of return values and argument registers in structure + ; doesn't matter for tailcall probe + + + ; setup ProfilePlatformSpecificData structure + xor r8, r8; + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 0h], r8 ; r8 is null -- struct functionId field + save_reg_postrsp rbp, OFFSETOF_PLATFORM_SPECIFIC_DATA + 8h ; -- struct rbp field + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 10h], rax ; caller rsp -- struct probeRsp field + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 18h], r11 ; return address -- struct ip field + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 20h], rdx ; -- struct profiledRsp field + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 28h], r8 ; r8 is null -- struct rax field + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 30h], r8 ; r8 is null -- struct hiddenArg field + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 38h], r8 ; r8 is null -- struct flt0 field + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 40h], r8 ; r8 is null -- struct flt1 field + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 48h], r8 ; r8 is null -- struct flt2 field + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 50h], r8 ; r8 is null -- struct flt3 field + mov r10, PROFILE_TAILCALL + mov [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA + 58h], r10d ; flags -- struct flags field + + ; we need to be able to restore the fp return register + save_xmm128_postrsp xmm0, OFFSETOF_FP_ARG_SPILL + 0h + END_PROLOGUE + + ; rcx already contains the clientInfo + lea rdx, [rsp + OFFSETOF_PLATFORM_SPECIFIC_DATA] + call ProfileTailcall + + ; restore fp return register + movdqa xmm0, [rsp + OFFSETOF_FP_ARG_SPILL + 0h] + + ; begin epilogue + add rsp, SIZEOF_STACK_FRAME + pop rax + ret +NESTED_END ProfileTailcallNaked, _TEXT + + +;; extern "C" DWORD __stdcall getcpuid(DWORD arg, unsigned char result[16]); +NESTED_ENTRY getcpuid, _TEXT + + push_nonvol_reg rbx + push_nonvol_reg rsi + END_PROLOGUE + + mov eax, ecx ; first arg + mov rsi, rdx ; second arg (result) + xor ecx, ecx ; clear ecx - needed for "Structured Extended Feature Flags" + cpuid + mov [rsi+ 0], eax + mov [rsi+ 4], ebx + mov [rsi+ 8], ecx + mov [rsi+12], edx + pop rsi + pop rbx + ret +NESTED_END getcpuid, _TEXT + + +;; extern "C" DWORD __stdcall xmmYmmStateSupport(); +LEAF_ENTRY xmmYmmStateSupport, _TEXT + mov ecx, 0 ; Specify xcr0 + xgetbv ; result in EDX:EAX + and eax, 06H + cmp eax, 06H ; check OS has enabled both XMM and YMM state support + jne not_supported + mov eax, 1 + jmp done + not_supported: + mov eax, 0 + done: + ret +LEAF_END xmmYmmStateSupport, _TEXT + +;The following function uses Deterministic Cache Parameter leafs to determine the cache hierarchy information on Prescott & Above platforms. +; This function takes 3 arguments: +; Arg1 is an input to ECX. Used as index to specify which cache level to return information on by CPUID. +; Arg1 is already passed in ECX on call to getextcpuid, so no explicit assignment is required; +; Arg2 is an input to EAX. For deterministic code enumeration, we pass in 4H in arg2. +; Arg3 is a pointer to the return dwbuffer +NESTED_ENTRY getextcpuid, _TEXT + push_nonvol_reg rbx + push_nonvol_reg rsi + END_PROLOGUE + + mov eax, edx ; second arg (input to EAX) + mov rsi, r8 ; third arg (pointer to return dwbuffer) + cpuid + mov [rsi+ 0], eax + mov [rsi+ 4], ebx + mov [rsi+ 8], ecx + mov [rsi+12], edx + pop rsi + pop rbx + + ret +NESTED_END getextcpuid, _TEXT + + +; EXTERN_C void moveOWord(LPVOID* src, LPVOID* target); +; +; MOVDQA is not an atomic operation. You need to call this function in a crst. +; +LEAF_ENTRY moveOWord, _TEXT + movdqa xmm0, [rcx] + movdqa [rdx], xmm0 + + ret +LEAF_END moveOWord, _TEXT + + +extern JIT_InternalThrowFromHelper:proc + +LEAF_ENTRY SinglecastDelegateInvokeStub, _TEXT + + test rcx, rcx + jz NullObject + + + mov rax, [rcx + OFFSETOF__DelegateObject___methodPtr] + mov rcx, [rcx + OFFSETOF__DelegateObject___target] ; replace "this" pointer + + jmp rax + +NullObject: + mov rcx, CORINFO_NullReferenceException_ASM + jmp JIT_InternalThrow + +LEAF_END SinglecastDelegateInvokeStub, _TEXT + + end + diff --git a/src/vm/amd64/AsmMacros.inc b/src/vm/amd64/AsmMacros.inc new file mode 100644 index 0000000000..f95a291929 --- /dev/null +++ b/src/vm/amd64/AsmMacros.inc @@ -0,0 +1,442 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; Define macros to build unwind data for prologues. +; + +push_nonvol_reg macro Reg + + .errnz ___STACK_ADJUSTMENT_FORBIDDEN, + + push Reg + .pushreg Reg + + endm + +push_vol_reg macro Reg + + .errnz ___STACK_ADJUSTMENT_FORBIDDEN, push_vol_reg cannot be used after save_reg_postrsp + + push Reg + .allocstack 8 + + endm + +push_eflags macro + + .errnz ___STACK_ADJUSTMENT_FORBIDDEN, push_eflags cannot be used after save_reg_postrsp + + pushfq + .allocstack 8 + + endm + +alloc_stack macro Size + + .errnz ___STACK_ADJUSTMENT_FORBIDDEN, alloc_stack cannot be used after save_reg_postrsp + + sub rsp, Size + .allocstack Size + + endm + +save_reg_frame macro Reg, FrameReg, Offset + + .erre ___FRAME_REG_SET, save_reg_frame cannot be used before set_frame + + mov Offset[FrameReg], Reg + .savereg Reg, Offset + + endm + +save_reg_postrsp macro Reg, Offset + + .errnz ___FRAME_REG_SET, save_reg_postrsp cannot be used after set_frame + + mov Offset[rsp], Reg + .savereg Reg, Offset + + ___STACK_ADJUSTMENT_FORBIDDEN = 1 + + endm + +save_xmm128_frame macro Reg, FrameReg, Offset + + .erre ___FRAME_REG_SET, save_xmm128_frame cannot be used before set_frame + + movdqa Offset[FrameReg], Reg + .savexmm128 Reg, Offset + + endm + +save_xmm128_postrsp macro Reg, Offset + + .errnz ___FRAME_REG_SET, save_reg_postrsp cannot be used after set_frame + + movdqa Offset[rsp], Reg + .savexmm128 Reg, Offset + + ___STACK_ADJUSTMENT_FORBIDDEN = 1 + + endm + +set_frame macro Reg, Offset + + .errnz ___FRAME_REG_SET, set_frame cannot be used more than once + +if Offset + + lea Reg, Offset[rsp] + +else + + mov reg, rsp + +endif + + .setframe Reg, Offset + ___FRAME_REG_SET = 1 + + endm + +END_PROLOGUE macro + + .endprolog + + endm + + +; +; Define function entry/end macros. +; + +LEAF_ENTRY macro Name, Section + +Section segment para 'CODE' + + align 16 + + public Name +Name proc + + endm + +LEAF_END macro Name, section + +Name endp + +Section ends + + endm + +LEAF_END_MARKED macro Name, section + public Name&_End +Name&_End label qword + ; this nop is important to keep the label in + ; the right place in the face of BBT + nop + +Name endp + +Section ends + + endm + + +NESTED_ENTRY macro Name, Section, Handler + +Section segment para 'CODE' + + align 16 + + public Name + +ifb + +Name proc frame + +else + +Name proc frame:Handler + +endif + + ___FRAME_REG_SET = 0 + ___STACK_ADJUSTMENT_FORBIDDEN = 0 + + endm + +NESTED_END macro Name, section + +Name endp + +Section ends + + endm + +NESTED_END_MARKED macro Name, section + public Name&_End +Name&_End label qword + +Name endp + +Section ends + + endm + + +; +; Macro to Call GetThread() correctly whether it is indirect or direct +; +CALL_GETTHREAD macro +ifndef GetThread +extern GetThread:proc +endif + call GetThread + endm + +CALL_GETAPPDOMAIN macro +ifndef GetAppDomain +extern GetAppDomain:proc +endif + call GetAppDomain + endm + +; +; if you change this code there will be corresponding code in JITInterfaceGen.cpp which will need to be changed +; + +; DEFAULT_TARGET needs to always be futher away than the fixed up target will be + + +JIT_HELPER_MONITOR_THUNK macro THUNK_NAME, Section +Section segment para 'CODE' + align 16 + public THUNK_NAME +THUNK_NAME proc + xor edx, edx +THUNK_NAME endp +Section ends + endm + +; +; Useful for enabling C++ to know where to patch code at runtime. +; +PATCH_LABEL macro Name + public Name +Name:: + endm + +; +; Define alternate entry macro. +; +ALTERNATE_ENTRY macro Name + public Name +Name label proc + endm + +; +; Appropriate instructions for certain specific scenarios: +; - REPRET: should be used as the return instruction when the return is a branch +; target or immediately follows a conditional branch +; - TAILJMP_RAX: ("jmp rax") should be used for tailcalls, this emits an instruction +; sequence which is recognized by the unwinder as a valid epilogue terminator +; +REPRET TEXTEQU +TAILJMP_RAX TEXTEQU + +NOP_2_BYTE macro + + xchg ax,ax + + endm + +NOP_3_BYTE macro + + nop dword ptr [rax] + + endm + +PUSH_CALLEE_SAVED_REGISTERS macro + + push_nonvol_reg r15 + push_nonvol_reg r14 + push_nonvol_reg r13 + push_nonvol_reg r12 + push_nonvol_reg rbp + push_nonvol_reg rbx + push_nonvol_reg rsi + push_nonvol_reg rdi + + endm + +SAVE_CALLEE_SAVED_REGISTERS macro ofs + + save_reg_postrsp rdi, ofs + 0h + save_reg_postrsp rsi, ofs + 8h + save_reg_postrsp rbx, ofs + 10h + save_reg_postrsp rbp, ofs + 18h + save_reg_postrsp r12, ofs + 20h + save_reg_postrsp r13, ofs + 28h + save_reg_postrsp r14, ofs + 30h + save_reg_postrsp r15, ofs + 38h + + endm + +POP_CALLEE_SAVED_REGISTERS macro + + pop rdi + pop rsi + pop rbx + pop rbp + pop r12 + pop r13 + pop r14 + pop r15 + + endm + +SAVE_ARGUMENT_REGISTERS macro ofs + + save_reg_postrsp rcx, ofs + 0h + save_reg_postrsp rdx, ofs + 8h + save_reg_postrsp r8, ofs + 10h + save_reg_postrsp r9, ofs + 18h + + endm + +RESTORE_ARGUMENT_REGISTERS macro ofs + + mov rcx, [rsp + ofs + 0h] + mov rdx, [rsp + ofs + 8h] + mov r8, [rsp + ofs + 10h] + mov r9, [rsp + ofs + 18h] + + endm + +SAVE_FLOAT_ARGUMENT_REGISTERS macro ofs + + save_xmm128_postrsp xmm0, ofs + save_xmm128_postrsp xmm1, ofs + 10h + save_xmm128_postrsp xmm2, ofs + 20h + save_xmm128_postrsp xmm3, ofs + 30h + + endm + +RESTORE_FLOAT_ARGUMENT_REGISTERS macro ofs + + movdqa xmm0, [rsp + ofs] + movdqa xmm1, [rsp + ofs + 10h] + movdqa xmm2, [rsp + ofs + 20h] + movdqa xmm3, [rsp + ofs + 30h] + + endm + + +; Stack layout: +; +; (stack parameters) +; ... +; r9 +; r8 +; rdx +; rcx <- __PWTB_ArgumentRegisters +; return address +; CalleeSavedRegisters::r15 +; CalleeSavedRegisters::r14 +; CalleeSavedRegisters::r13 +; CalleeSavedRegisters::r12 +; CalleeSavedRegisters::rbp +; CalleeSavedRegisters::rbx +; CalleeSavedRegisters::rsi +; CalleeSavedRegisters::rdi <- __PWTB_StackAlloc +; padding to align xmm save area +; xmm3 +; xmm2 +; xmm1 +; xmm0 <- __PWTB_FloatArgumentRegisters +; extra locals + padding to qword align +; callee's r9 +; callee's r8 +; callee's rdx +; callee's rcx + +PROLOG_WITH_TRANSITION_BLOCK macro extraLocals := <0>, stackAllocOnEntry := <0>, stackAllocSpill1, stackAllocSpill2, stackAllocSpill3 + + __PWTB_FloatArgumentRegisters = SIZEOF_MAX_OUTGOING_ARGUMENT_HOMES + extraLocals + + if (__PWTB_FloatArgumentRegisters mod 16) ne 0 + __PWTB_FloatArgumentRegisters = __PWTB_FloatArgumentRegisters + 8 + endif + + __PWTB_StackAlloc = __PWTB_FloatArgumentRegisters + 4 * 16 + 8 + __PWTB_TransitionBlock = __PWTB_StackAlloc + __PWTB_ArgumentRegisters = __PWTB_StackAlloc + 9 * 8 + + .errnz stackAllocOnEntry ge 4*8, Max supported stackAllocOnEntry is 3*8 + + if stackAllocOnEntry gt 0 + .allocstack stackAllocOnEntry + endif + + ; PUSH_CALLEE_SAVED_REGISTERS expanded here + + if stackAllocOnEntry lt 8 + push_nonvol_reg r15 + endif + + if stackAllocOnEntry lt 2*8 + push_nonvol_reg r14 + endif + + if stackAllocOnEntry lt 3*8 + push_nonvol_reg r13 + endif + + push_nonvol_reg r12 + push_nonvol_reg rbp + push_nonvol_reg rbx + push_nonvol_reg rsi + push_nonvol_reg rdi + + alloc_stack __PWTB_StackAlloc + SAVE_ARGUMENT_REGISTERS __PWTB_ArgumentRegisters + SAVE_FLOAT_ARGUMENT_REGISTERS __PWTB_FloatArgumentRegisters + + if stackAllocOnEntry ge 3*8 + mov stackAllocSpill3, [rsp + __PWTB_StackAlloc + 28h] + save_reg_postrsp r13, __PWTB_StackAlloc + 28h + endif + + if stackAllocOnEntry ge 2*8 + mov stackAllocSpill2, [rsp + __PWTB_StackAlloc + 30h] + save_reg_postrsp r14, __PWTB_StackAlloc + 30h + endif + + if stackAllocOnEntry ge 8 + mov stackAllocSpill1, [rsp + __PWTB_StackAlloc + 38h] + save_reg_postrsp r15, __PWTB_StackAlloc + 38h + endif + + END_PROLOGUE + + endm + +EPILOG_WITH_TRANSITION_BLOCK_RETURN macro + + add rsp, __PWTB_StackAlloc + POP_CALLEE_SAVED_REGISTERS + ret + + endm + +EPILOG_WITH_TRANSITION_BLOCK_TAILCALL macro + + RESTORE_FLOAT_ARGUMENT_REGISTERS __PWTB_FloatArgumentRegisters + RESTORE_ARGUMENT_REGISTERS __PWTB_ArgumentRegisters + add rsp, __PWTB_StackAlloc + POP_CALLEE_SAVED_REGISTERS + + endm diff --git a/src/vm/amd64/CLRErrorReporting.vrg b/src/vm/amd64/CLRErrorReporting.vrg new file mode 100644 index 0000000000..f398185aff --- /dev/null +++ b/src/vm/amd64/CLRErrorReporting.vrg @@ -0,0 +1,5 @@ +VSREG 7 + +[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Application\.NET Runtime 4.0 Error Reporting] +"EventMessageFile"="[DWFolder.F0DF3458_A845_11D3_8D0A_0050046416B9]DW20.EXE" +"TypesSupported"=dword:00000007 diff --git a/src/vm/amd64/CallDescrWorkerAMD64.asm b/src/vm/amd64/CallDescrWorkerAMD64.asm new file mode 100644 index 0000000000..7607a421f2 --- /dev/null +++ b/src/vm/amd64/CallDescrWorkerAMD64.asm @@ -0,0 +1,132 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +include +include + +extern CallDescrWorkerUnwindFrameChainHandler:proc + +;; +;; EXTERN_C void FastCallFinalizeWorker(Object *obj, PCODE funcPtr); +;; + NESTED_ENTRY FastCallFinalizeWorker, _TEXT, CallDescrWorkerUnwindFrameChainHandler + alloc_stack 28h ;; alloc callee scratch and align the stack + END_PROLOGUE + + ; + ; RCX: already contains obj* + ; RDX: address of finalizer method to call + ; + + ; !!!!!!!!! + ; NOTE: you cannot tail call here because we must have the CallDescrWorkerUnwindFrameChainHandler + ; personality routine on the stack. + ; !!!!!!!!! + call rdx + xor rax, rax + + ; epilog + add rsp, 28h + ret + + + NESTED_END FastCallFinalizeWorker, _TEXT + +;;extern "C" void CallDescrWorkerInternal(CallDescrData * pCallDescrData); + + NESTED_ENTRY CallDescrWorkerInternal, _TEXT, CallDescrWorkerUnwindFrameChainHandler + + push_nonvol_reg rbx ; save nonvolatile registers + push_nonvol_reg rsi ; + push_nonvol_reg rbp ; + set_frame rbp, 0 ; set frame pointer + + END_PROLOGUE + + mov rbx, rcx ; save pCallDescrData in rbx + + mov ecx, dword ptr [rbx + CallDescrData__numStackSlots] + + test ecx, 1 + jz StackAligned + push rax +StackAligned: + + mov rsi, [rbx + CallDescrData__pSrc] ; set source argument list address + lea rsi, [rsi + 8 * rcx] + +StackCopyLoop: ; copy the arguments to stack top-down to carefully probe for sufficient stack space + sub rsi, 8 + push qword ptr [rsi] + dec ecx + jnz StackCopyLoop + + ; + ; N.B. All four argument registers are loaded regardless of the actual number + ; of arguments. + ; + + mov rax, [rbx + CallDescrData__dwRegTypeMap] ; save the reg (arg) type map + + mov rcx, 0[rsp] ; load first four argument registers + movss xmm0, real4 ptr 0[rsp] ; + cmp al, ASM_ELEMENT_TYPE_R8 ; + jnz Arg2 ; + movsd xmm0, real8 ptr 0[rsp] ; +Arg2: + mov rdx, 8[rsp] ; + movss xmm1, real4 ptr 8[rsp] ; + cmp ah, ASM_ELEMENT_TYPE_R8 ; + jnz Arg3 ; + movsd xmm1, real8 ptr 8[rsp] ; +Arg3: + mov r8, 10h[rsp] ; + movss xmm2, real4 ptr 10h[rsp]; + shr eax, 16 ; + cmp al, ASM_ELEMENT_TYPE_R8 ; + jnz Arg4 ; + movsd xmm2, real8 ptr 10h[rsp]; +Arg4: + mov r9, 18h[rsp] ; + movss xmm3, real4 ptr 18h[rsp]; + cmp ah, ASM_ELEMENT_TYPE_R8 ; + jnz DoCall ; + movsd xmm3, real8 ptr 18h[rsp]; +DoCall: + call qword ptr [rbx+CallDescrData__pTarget] ; call target function + + ; Save FP return value + + mov ecx, dword ptr [rbx+CallDescrData__fpReturnSize] + test ecx, ecx + jz ReturnsInt + + cmp ecx, 4 + je ReturnsFloat + cmp ecx, 8 + je ReturnsDouble + ; unexpected + jmp Epilog + +ReturnsInt: + mov [rbx+CallDescrData__returnValue], rax + +Epilog: + lea rsp, 0[rbp] ; deallocate argument list + pop rbp ; restore nonvolatile register + pop rsi ; + pop rbx ; + ret + +ReturnsFloat: + movss real4 ptr [rbx+CallDescrData__returnValue], xmm0 + jmp Epilog + +ReturnsDouble: + movsd real8 ptr [rbx+CallDescrData__returnValue], xmm0 + jmp Epilog + + NESTED_END CallDescrWorkerInternal, _TEXT + + end diff --git a/src/vm/amd64/ComCallPreStub.asm b/src/vm/amd64/ComCallPreStub.asm new file mode 100644 index 0000000000..931294eb87 --- /dev/null +++ b/src/vm/amd64/ComCallPreStub.asm @@ -0,0 +1,158 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; ==++== +; + +; +; ==--== + +ifdef FEATURE_COMINTEROP + +include AsmMacros.inc +include asmconstants.inc + +; extern "C" const BYTE* ComPreStubWorker(ComPrestubMethodFrame *pPFrame, UINT64 *pErrorResult) +extern ComPreStubWorker:proc +extern JIT_FailFast:proc +extern s_gsCookie:qword + + +; extern "C" VOID ComCallPreStub() +NESTED_ENTRY ComCallPreStub, _TEXT + +; +; Stack layout: +; +; (stack parameters) +; ... +; r9 +; r8 +; rdx +; rcx +; ComPrestubMethodFrame::m_ReturnAddress +; ComPrestubMethodFrame::m_pFuncDesc +; Frame::m_Next +; __VFN_table <-- rsp + ComCallPreStub_ComPrestubMethodFrame_OFFSET +; gsCookie +; HRESULT <-- rsp + ComCallPreStub_HRESULT_OFFSET +; (optional padding to qword align xmm save area) +; xmm3 +; xmm2 +; xmm1 +; xmm0 <-- rsp + ComCallPreStub_XMM_SAVE_OFFSET +; callee's r9 +; callee's r8 +; callee's rdx +; callee's rcx + +ComCallPreStub_STACK_FRAME_SIZE = 0 + +; ComPrestubMethodFrame MUST be the highest part of the stack frame, +; immediately below the return address, so that +; ComPrestubMethodFrame::m_ReturnAddress and m_FuncDesc are in the right place. +ComCallPreStub_STACK_FRAME_SIZE = ComCallPreStub_STACK_FRAME_SIZE + SIZEOF__ComPrestubMethodFrame - 8 +ComCallPreStub_ComPrestubMethodFrame_NEGOFFSET = ComCallPreStub_STACK_FRAME_SIZE + +; CalleeSavedRegisters MUST be immediately below ComPrestubMethodFrame +ComCallPreStub_STACK_FRAME_SIZE = ComCallPreStub_STACK_FRAME_SIZE + 8*8 +ComCallPreStub_CalleeSavedRegisters_NEGOFFSET = ComCallPreStub_STACK_FRAME_SIZE + +; GSCookie MUST be immediately below CalleeSavedRegisters +ComCallPreStub_STACK_FRAME_SIZE = ComCallPreStub_STACK_FRAME_SIZE + SIZEOF_GSCookie + +; UINT64 (out param to ComPreStubWorker) +ComCallPreStub_STACK_FRAME_SIZE = ComCallPreStub_STACK_FRAME_SIZE + 8 +ComCallPreStub_ERRORRETVAL_NEGOFFSET = ComCallPreStub_STACK_FRAME_SIZE + +; Ensure that the offset of the XMM save area will be 16-byte aligned. +if ((ComCallPreStub_STACK_FRAME_SIZE + SIZEOF__Frame + 8) mod 16) ne 0 +ComCallPreStub_STACK_FRAME_SIZE = ComCallPreStub_STACK_FRAME_SIZE + 8 +endif + +; FP parameters (xmm0-xmm3) +ComCallPreStub_STACK_FRAME_SIZE = ComCallPreStub_STACK_FRAME_SIZE + 40h +ComCallPreStub_XMM_SAVE_NEGOFFSET = ComCallPreStub_STACK_FRAME_SIZE + +; Callee scratch area +ComCallPreStub_STACK_FRAME_SIZE = ComCallPreStub_STACK_FRAME_SIZE + 20h + +; Now we have the full size of the stack frame. The offsets have been computed relative to the +; top, so negate them to make them relative to the post-prologue rsp. +ComCallPreStub_ComPrestubMethodFrame_OFFSET = ComCallPreStub_STACK_FRAME_SIZE - ComCallPreStub_ComPrestubMethodFrame_NEGOFFSET +OFFSETOF_GSCookie = ComCallPreStub_ComPrestubMethodFrame_OFFSET - SIZEOF_GSCookie +ComCallPreStub_ERRORRETVAL_OFFSET = ComCallPreStub_STACK_FRAME_SIZE - ComCallPreStub_ERRORRETVAL_NEGOFFSET +ComCallPreStub_XMM_SAVE_OFFSET = ComCallPreStub_STACK_FRAME_SIZE - ComCallPreStub_XMM_SAVE_NEGOFFSET + + .allocstack 8 ; ComPrestubMethodFrame::m_pFuncDesc, pushed by prepad + + alloc_stack SIZEOF__ComPrestubMethodFrame - 2*8 + + ; + ; Save ComPrestubMethodFrame* to pass to ComPreStubWorker + ; + mov r10, rsp + + ; + ; Allocate callee scratch area and save FP parameters + ; + alloc_stack ComCallPreStub_ComPrestubMethodFrame_OFFSET + + ; + ; Save argument registers + ; + SAVE_ARGUMENT_REGISTERS ComCallPreStub_STACK_FRAME_SIZE + 8h + + ; + ; spill the fp args + ; + SAVE_FLOAT_ARGUMENT_REGISTERS ComCallPreStub_XMM_SAVE_OFFSET + + END_PROLOGUE + + mov rcx, s_gsCookie + mov [rsp + OFFSETOF_GSCookie], rcx + ; + ; Resolve target. + ; + mov rcx, r10 + lea rdx, [rsp + ComCallPreStub_ERRORRETVAL_OFFSET] + call ComPreStubWorker + test rax, rax + jz ExitError + +ifdef _DEBUG + mov rcx, s_gsCookie + cmp [rsp + OFFSETOF_GSCookie], rcx + je GoodGSCookie + call JIT_FailFast +GoodGSCookie: +endif ; _DEBUG + + ; + ; Restore FP parameters + ; + RESTORE_FLOAT_ARGUMENT_REGISTERS ComCallPreStub_XMM_SAVE_OFFSET + + ; + ; Restore integer parameters + ; + RESTORE_ARGUMENT_REGISTERS ComCallPreStub_STACK_FRAME_SIZE + 8h + + add rsp, ComCallPreStub_ComPrestubMethodFrame_OFFSET + SIZEOF__ComPrestubMethodFrame - 8 + + TAILJMP_RAX + +ExitError: + mov rax, [rsp + ComCallPreStub_ERRORRETVAL_OFFSET] + add rsp, ComCallPreStub_ComPrestubMethodFrame_OFFSET + SIZEOF__ComPrestubMethodFrame - 8 + + ret + +NESTED_END ComCallPreStub, _TEXT + +endif ; FEATURE_COMINTEROP + + end + diff --git a/src/vm/amd64/CrtHelpers.asm b/src/vm/amd64/CrtHelpers.asm new file mode 100644 index 0000000000..6ec6e4d2a9 --- /dev/null +++ b/src/vm/amd64/CrtHelpers.asm @@ -0,0 +1,528 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; ==++== +; + +; +; ==--== +; *********************************************************************** +; File: CrtHelpers.asm, see history in asmhelpers.asm +; +; *********************************************************************** + +include AsmMacros.inc +include asmconstants.inc + +; JIT_MemSet/JIT_MemCpy +; +; It is IMPORANT that the exception handling code is able to find these guys +; on the stack, but to keep them from being tailcalled by VC++ we need to turn +; off optimization and it ends up being a wasteful implementation. +; +; Hence these assembly helpers. +; + + +;*** +;memset.asm - set a section of memory to all one byte +; +; 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.; +; +;******************************************************************************* + +;*** +;char *memset(dst, value, count) - sets "count" bytes at "dst" to "value" +; +;Purpose: +; Sets the first "count" bytes of the memory starting +; at "dst" to the character value "value". +; +; Algorithm: +; char * +; memset (dst, value, count) +; char *dst; +; char value; +; unsigned int count; +; { +; char *start = dst; +; +; while (count--) +; *dst++ = value; +; return(start); +; } +; +;Entry: +; char *dst - pointer to memory to fill with value +; char value - value to put in dst bytes +; int count - number of bytes of dst to fill +; +;Exit: +; returns dst, with filled bytes +; +;Uses: +; +;Exceptions: +; +;******************************************************************************* + +CACHE_LIMIT_MEMSET equ 070000h ; limit for nontemporal fill + +LEAF_ENTRY JIT_MemSet, _TEXT + + mov rax, rcx ; save destination address + cmp r8, 8 ; check if 8 bytes to fill + jb short mset40 ; if b, less than 8 bytes to fill + movzx edx, dl ; set fill pattern + mov r9, 0101010101010101h ; replicate fill over 8 bytes + imul rdx, r9 ; + cmp r8, 64 ; check if 64 bytes to fill + jb short mset20 ; if b, less than 64 bytes + +; +; Large block - fill alignment bytes. +; + +mset00: neg rcx ; compute bytes to alignment + and ecx, 7 ; + jz short mset10 ; if z, no alignment required + sub r8, rcx ; adjust remaining bytes by alignment + mov [rax], rdx ; fill alignment bytes +mset10: add rcx, rax ; compute aligned destination address + +; +; Attempt to fill 64-byte blocks +; + + mov r9, r8 ; copy count of bytes remaining + and r8, 63 ; compute remaining byte count + shr r9, 6 ; compute number of 64-byte blocks + test r9, r9 ; remove partial flag stall caused by shr + jnz short mset70 ; if nz, 64-byte blocks to fill + +; +; Fill 8-byte bytes. +; + +mset20: mov r9, r8 ; copy count of bytes remaining + and r8, 7 ; compute remaining byte count + shr r9, 3 ; compute number of 8-byte blocks + test r9, r9 ; remove partial flag stall caused by shr + jz short mset40 ; if z, no 8-byte blocks + + align ; simpler way to align instrucitons + +mset30: mov [rcx], rdx ; fill 8-byte blocks + add rcx, 8 ; advance to next 8-byte block + dec r9 ; decrement loop count + jnz short mset30 ; if nz, more 8-byte blocks + +; +; Fill residual bytes. +; + +mset40: test r8, r8 ; test if any bytes to fill + jz short mset60 ; if z, no bytes to fill +mset50: mov [rcx], dl ; fill byte + inc rcx ; advance to next byte + dec r8 ; decrement loop count + jnz short mset50 ; if nz, more bytes to fill +mset60: + ; for some reason the assembler doesn't like the REPRET macro on the same line as a label + REPRET ; return + +; +; Fill 64-byte blocks. +; + + align 16 + + db 066h, 066h, 066h, 090h + db 066h, 066h, 090h + +mset70: cmp r9, CACHE_LIMIT_MEMSET / 64 ; check if large fill + jae short mset90 ; if ae, large fill +mset80: mov [rcx], rdx ; fill 64-byte block + mov 8[rcx], rdx ; + mov 16[rcx], rdx ; + add rcx, 64 ; advance to next block + mov (24 - 64)[rcx], rdx ; + mov (32 - 64)[rcx], rdx ; + dec r9 ; decrement loop count + mov (40 - 64)[rcx], rdx ; + mov (48 - 64)[rcx], rdx ; + mov (56 - 64)[rcx], rdx ; + jnz short mset80 ; if nz, more 64-byte blocks + jmp short mset20 ; finish in common code + +; +; Fill 64-byte blocks nontemporal. +; + + align + +mset90: movnti [rcx], rdx ; fill 64-byte block + movnti 8[rcx], rdx ; + movnti 16[rcx], rdx ; + add rcx, 64 ; advance to next block + movnti (24 - 64)[rcx], rdx ; + movnti (32 - 64)[rcx], rdx ; + dec r9 ; decrement loop count + movnti (40 - 64)[rcx], rdx ; + movnti (48 - 64)[rcx], rdx ; + movnti (56 - 64)[rcx], rdx ; + jnz short mset90 ; if nz, move 64-byte blocks + lock or byte ptr [rsp], 0 ; flush data to memory + jmp mset20 ; finish in common code + +LEAF_END_MARKED JIT_MemSet, _TEXT + +;******************************************************************************* +; This ensures that atomic updates of aligned fields will stay atomic. +;*** +;JIT_MemCpy - Copy source buffer to destination buffer +; +;Purpose: +;JIT_MemCpy - Copy source buffer to destination buffer +; +;Purpose: +; JIT_MemCpy() copies a source memory buffer to a destination memory +; buffer. This routine recognize overlapping buffers to avoid propogation. +; For cases where propogation is not a problem, memcpy() can be used. +; +;Entry: +; void *dst = pointer to destination buffer +; const void *src = pointer to source buffer +; size_t count = number of bytes to copy +; +;Exit: +; Returns a pointer to the destination buffer in AX/DX:AX +; +;Uses: +; CX, DX +; +;Exceptions: +;******************************************************************************* +; This ensures that atomic updates of aligned fields will stay atomic. + +CACHE_LIMIT_MEMMOV equ 040000h ; limit for nontemporal fill +CACHE_BLOCK equ 01000h ; nontemporal move block size + + +LEAF_ENTRY JIT_MemCpy, _TEXT + + mov r11, rcx ; save destination address + sub rdx, rcx ; compute offset to source buffer + jb mmov10 ; if b, destination may overlap + cmp r8, 8 ; check if 8 bytes to move + jb short mcpy40 ; if b, less than 8 bytes to move + +; +; Move alignment bytes. +; + + test cl, 7 ; test if destination aligned + jz short mcpy20 ; if z, destination aligned + test cl, 1 ; test if byte move needed + jz short mcpy00 ; if z, byte move not needed + mov al, [rcx + rdx] ; move byte + dec r8 ; decrement byte count + mov [rcx], al ; + inc rcx ; increment destination address +mcpy00: test cl, 2 ; test if word move needed + jz short mcpy10 ; if z, word move not needed + mov ax, [rcx + rdx] ; move word + sub r8, 2 ; reduce byte count + mov [rcx], ax ; + add rcx, 2 ; advance destination address +mcpy10: test cl, 4 ; test if dword move needed + jz short mcpy20 ; if z, dword move not needed + mov eax, [rcx + rdx] ; move dword + sub r8, 4 ; reduce byte count + mov [rcx], eax ; + add rcx, 4 ; advance destination address + +; +; Attempt to move 32-byte blocks. +; + +mcpy20: mov r9, r8 ; copy count of bytes remaining + shr r9, 5 ; compute number of 32-byte blocks + test r9, r9 ; v-liti, remove partial flag stall caused by shr + jnz short mcpy60 ; if nz, 32-byte blocks to fill + + align +; +; Move 8-byte blocks. +; + +mcpy25: mov r9, r8 ; copy count of bytes remaining + shr r9, 3 ; compute number of 8-byte blocks + test r9, r9 ; v-liti, remove partial flag stall caused by shr + jz short mcpy40 ; if z, no 8-byte blocks + align + +mcpy30: mov rax, [rcx + rdx] ; move 8-byte blocks + mov [rcx], rax ; + add rcx, 8 ; advance destination address + dec r9 ; decrement loop count + jnz short mcpy30 ; if nz, more 8-byte blocks + and r8, 7 ; compute remaining byte count + +; +; Test for residual bytes. +; + +mcpy40: test r8, r8 ; test if any bytes to move + jnz short mcpy50 ; if nz, residual bytes to move + mov rax, r11 ; set destination address + ret ; + +; +; Move residual bytes. +; + + align + +mcpy50: mov al, [rcx + rdx] ; move byte + mov [rcx], al ; + inc rcx ; increment destiantion address + dec r8 ; decrement loop count + jnz short mcpy50 ; if nz, more bytes to fill + mov rax, r11 ; set destination address + ret ; return + +; +; Move 32 byte blocks +; + + align 16 + + db 066h, 066h, 066h, 090h + db 066h, 066h, 090h + +mcpy60: cmp r9, CACHE_LIMIT_MEMMOV / 32 ; check if large move + jae short mcpy80 ; if ae, large move +mcpy70: mov rax, [rcx + rdx] ; move 32-byte block + mov r10, 8[rcx + rdx] ; + add rcx, 32 ; advance destination address + mov (-32)[rcx], rax ; + mov (-24)[rcx], r10 ; + mov rax, (-16)[rcx + rdx] ; + mov r10, (-8)[rcx + rdx] ; + dec r9 ; + mov (-16)[rcx], rax ; + mov (-8)[rcx], r10 ; + jnz short mcpy70 ; if nz, more 32-byte blocks + and r8, 31 ; compute remaining byte count + jmp mcpy25 ; + +; +; Move 64-byte blocks nontemporal. +; + + align + + db 066h, 090h + +mcpy80: cmp rdx, CACHE_BLOCK ; check if cache block spacing + jb short mcpy70 ; if b, not cache block spaced +mcpy81: mov eax, CACHE_BLOCK / 128 ; set loop count +mcpy85: prefetchnta [rcx + rdx] ; prefetch 128 bytes + prefetchnta 64[rcx + rdx] ; + add rcx, 128 ; advance source address + dec eax ; decrement loop count + jnz short mcpy85 ; if nz, more to prefetch + sub rcx, CACHE_BLOCK ; reset source address + mov eax, CACHE_BLOCK / 64 ; set loop count +mcpy90: mov r9, [rcx + rdx] ; move 64-byte block + mov r10, 8[rcx + rdx] ; + movnti [rcx], r9 ; + movnti 8[rcx], r10 ; + mov r9, 16[rcx + rdx] ; + mov r10, 24[rcx + rdx] ; + movnti 16[rcx], r9 ; + movnti 24[rcx], r10 ; + mov r9, 32[rcx + rdx] ; + mov r10, 40[rcx + rdx] ; + add rcx, 64 ; advance destination address + movnti (32 - 64)[rcx], r9 ; + movnti (40 - 64)[rcx], r10 ; + mov r9, (48 - 64)[rcx + rdx] ; + mov r10, (56 - 64)[rcx + rdx] ; + dec eax ; + movnti (48 - 64)[rcx], r9 ; + movnti (56 - 64)[rcx], r10 ; + jnz short mcpy90 ; if nz, more 32-byte blocks + sub r8, CACHE_BLOCK ; reduce remaining length + cmp r8, CACHE_BLOCK ; check if cache block remains + jae mcpy81 ; if ae, cache block remains + lock or byte ptr [rsp], 0 ; flush data to memory + jmp mcpy20 ; + +; +; The source address is less than the destination address. +; + + align + + db 066h, 066h, 066h, 090h + db 066h, 066h, 066h, 090h + db 066h, 090h + +mmov10: add rcx, r8 ; compute ending destination address + cmp r8, 8 ; check if 8 bytes to move + jb short mmov60 ; if b, less than 8 bytes to move + +; +; Move alignment bytes. +; + + test cl, 7 ; test if destination aligned + jz short mmov30 ; if z, destination aligned + test cl, 1 ; test if byte move needed + jz short mmov15 ; if z, byte move not needed + dec rcx ; decrement destination address + mov al, [rcx + rdx] ; move byte + dec r8 ; decrement byte count + mov [rcx], al ; +mmov15: test cl, 2 ; test if word move needed + jz short mmov20 ; if z, word move not needed + sub rcx, 2 ; reduce destination address + mov ax, [rcx + rdx] ; move word + sub r8, 2 ; reduce byte count + mov [rcx], ax ; +mmov20: test cl, 4 ; test if dword move needed + jz short mmov30 ; if z, dword move not needed + sub rcx, 4 ; reduce destination address + mov eax, [rcx + rdx] ; move dword + sub r8, 4 ; reduce byte count + mov [rcx], eax ; + +; +; Attempt to move 32-byte blocks +; + +mmov30: mov r9, r8 ; copy count of bytes remaining + shr r9, 5 ; compute number of 32-byte blocks + test r9, r9 ; v-liti, remove partial flag stall caused by shr + jnz short mmov80 ; if nz, 32-byte blocks to fill + +; +; Move 8-byte blocks. +; + align + +mmov40: mov r9, r8 ; copy count of bytes remaining + shr r9, 3 ; compute number of 8-byte blocks + test r9, r9 ; v-liti, remove partial flag stall caused by shr + jz short mmov60 ; if z, no 8-byte blocks + + align + +mmov50: sub rcx, 8 ; reduce destination address + mov rax, [rcx + rdx] ; move 8-byte blocks + dec r9 ; decrement loop count + mov [rcx], rax ; + jnz short mmov50 ; if nz, more 8-byte blocks + and r8, 7 ; compute remaining byte count + +; +; Test for residual bytes. +; + +mmov60: test r8, r8 ; test if any bytes to move + jnz short mmov70 ; if nz, residual bytes to move + mov rax, r11 ; set destination address + ret ; + +; +; Move residual bytes. +; + + align + +mmov70: dec rcx ; decrement destination address + mov al, [rcx + rdx] ; move byte + dec r8 ; decrement loop count + mov [rcx], al ; + jnz short mmov70 ; if nz, more bytes to fill + mov rax, r11 ; set destination address + ret ; return + +; +; Move 32 byte blocks +; + + align 16 + + db 066h, 066h, 066h, 090h + db 066h, 066h, 090h + +mmov80: cmp r9, CACHE_LIMIT_MEMMOV / 32 ; check if large move + jae short mmov93 ; if ae, large move +mmov90: mov rax, (-8)[rcx + rdx] ; move 32-byte block + mov r10, (-16)[rcx + rdx] ; + sub rcx, 32 ; reduce destination address + mov 24[rcx], rax ; + mov 16[rcx], r10 ; + mov rax, 8[rcx + rdx] ; + mov r10, [rcx + rdx] ; + dec r9 ; + mov 8[rcx], rax ; + mov [rcx], r10 ; + jnz short mmov90 ; if nz, more 32-byte blocks + and r8, 31 ; compute remaining byte count + jmp mmov40 ; + +; +; Move 64-byte blocks nontemporal. +; + + align + + db 066h, 090h + +mmov93: cmp rdx, -CACHE_BLOCK ; check if cache block spacing + ja short mmov90 ; if a, not cache block spaced +mmov94: mov eax, CACHE_BLOCK / 128 ; set loop count +mmov95: sub rcx, 128 ; reduce destination address + prefetchnta [rcx + rdx] ; prefetch 128 bytes + prefetchnta 64[rcx + rdx] ; + dec eax ; decrement loop count + jnz short mmov95 ; if nz, more to prefetch + add rcx, CACHE_BLOCK ; reset source address + mov eax, CACHE_BLOCK / 64 ; set loop count +mmov97: mov r9, (-8)[rcx + rdx] ; move 64-byte block + mov r10, (-16)[rcx + rdx] ; + movnti (-8)[rcx], r9 ; + movnti (-16)[rcx], r10 ; + mov r9, (-24)[rcx + rdx] ; + mov r10, (-32)[rcx + rdx] ; + movnti (-24)[rcx], r9 ; + movnti (-32)[rcx], r10 ; + mov r9, (-40)[rcx + rdx] ; + mov r10, (-48)[rcx + rdx] ; + sub rcx, 64 ; reduce destination address + movnti (64 - 40)[rcx], r9 ; + movnti (64 - 48)[rcx], r10 ; + mov r9, (64 - 56)[rcx + rdx] ; + mov r10, (64 - 64)[rcx + rdx] ; + dec eax ; decrement loop count + movnti (64 - 56)[rcx], r9 ; + movnti (64 - 64)[rcx], r10 ; + jnz short mmov97 ; if nz, more 32-byte blocks + sub r8, CACHE_BLOCK ; reduce remaining length + cmp r8, CACHE_BLOCK ; check if cache block remains + jae mmov94 ; if ae, cache block remains + lock or byte ptr [rsp], 0 ; flush data to memory + jmp mmov30 ; + +LEAF_END_MARKED JIT_MemCpy, _TEXT + + + end + diff --git a/src/vm/amd64/ExternalMethodFixupThunk.asm b/src/vm/amd64/ExternalMethodFixupThunk.asm new file mode 100644 index 0000000000..6c43762fd9 --- /dev/null +++ b/src/vm/amd64/ExternalMethodFixupThunk.asm @@ -0,0 +1,107 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +include +include AsmConstants.inc + + extern ExternalMethodFixupWorker:proc + extern ProcessCLRException:proc + extern VirtualMethodFixupWorker:proc + +ifdef FEATURE_READYTORUN + extern DynamicHelperWorker:proc +endif + +;============================================================================================ +;; EXTERN_C VOID __stdcall ExternalMethodFixupStub() + +NESTED_ENTRY ExternalMethodFixupStub, _TEXT, ProcessCLRException + + PROLOG_WITH_TRANSITION_BLOCK 0, 8, rdx + + lea rcx, [rsp + __PWTB_TransitionBlock] ; pTransitionBlock + sub rdx, 5 ; pThunk + mov r8, 0 ; sectionIndex + mov r9, 0 ; pModule + + call ExternalMethodFixupWorker + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL +PATCH_LABEL ExternalMethodFixupPatchLabel + TAILJMP_RAX + +NESTED_END ExternalMethodFixupStub, _TEXT + + +ifdef FEATURE_READYTORUN + +NESTED_ENTRY DelayLoad_MethodCall, _TEXT + + PROLOG_WITH_TRANSITION_BLOCK 0, 10h, r8, r9 + + lea rcx, [rsp + __PWTB_TransitionBlock] ; pTransitionBlock + mov rdx, rax ; pIndirection + + call ExternalMethodFixupWorker + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + + ; Share the patch label + jmp ExternalMethodFixupPatchLabel + +NESTED_END DelayLoad_MethodCall, _TEXT + +;============================================================================================ + +DYNAMICHELPER macro frameFlags, suffix + +NESTED_ENTRY DelayLoad_Helper&suffix, _TEXT + + PROLOG_WITH_TRANSITION_BLOCK 8h, 10h, r8, r9 + + mov qword ptr [rsp + SIZEOF_MAX_OUTGOING_ARGUMENT_HOMES], frameFlags + lea rcx, [rsp + __PWTB_TransitionBlock] ; pTransitionBlock + mov rdx, rax ; pIndirection + + call DynamicHelperWorker + + test rax,rax + jnz @F + + mov rax, [rsp + __PWTB_ArgumentRegisters] ; The result is stored in the argument area of the transition block + + EPILOG_WITH_TRANSITION_BLOCK_RETURN + +@@: + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + TAILJMP_RAX + +NESTED_END DelayLoad_Helper&suffix, _TEXT + + endm + +DYNAMICHELPER DynamicHelperFrameFlags_Default +DYNAMICHELPER DynamicHelperFrameFlags_ObjectArg, _Obj +DYNAMICHELPER , _ObjObj + +endif ; FEATURE_READYTORUN + +;============================================================================================ +;; EXTERN_C VOID __stdcall VirtualMethodFixupStub() + +NESTED_ENTRY VirtualMethodFixupStub, _TEXT, ProcessCLRException + + PROLOG_WITH_TRANSITION_BLOCK 0, 8, rdx + + lea rcx, [rsp + __PWTB_TransitionBlock] ; pTransitionBlock + sub rdx, 5 ; pThunk + call VirtualMethodFixupWorker + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL +PATCH_LABEL VirtualMethodFixupPatchLabel + TAILJMP_RAX + +NESTED_END VirtualMethodFixupStub, _TEXT + + end diff --git a/src/vm/amd64/GenericComCallStubs.asm b/src/vm/amd64/GenericComCallStubs.asm new file mode 100644 index 0000000000..b7e5e0f5ad --- /dev/null +++ b/src/vm/amd64/GenericComCallStubs.asm @@ -0,0 +1,304 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; ==++== +; + +; +; ==--== + +ifdef FEATURE_COMINTEROP + +include AsmMacros.inc +include asmconstants.inc + +extern CallDescrWorkerUnwindFrameChainHandler:proc +extern ReverseComUnwindFrameChainHandler:proc +extern COMToCLRWorker:proc +extern JIT_FailFast:proc +extern s_gsCookie:qword + + +NESTED_ENTRY GenericComCallStub, _TEXT, ReverseComUnwindFrameChainHandler + +; +; Set up a ComMethodFrame and call COMToCLRWorker. +; +; Stack frame layout: +; +; (stack parameters) +; ... +; r9 +; r8 +; rdx +; rcx +; UnmanagedToManagedFrame::m_ReturnAddress +; UnmanagedToManagedFrame::m_Datum +; Frame::m_Next +; __VFN_table <-- rsp + GenericComCallStub_ComMethodFrame_OFFSET +; GSCookie +; (optional padding to qword align xmm save area) +; xmm3 +; xmm2 +; xmm1 +; xmm0 <-- rsp + GenericComCallStub_XMM_SAVE_OFFSET +; r12 +; r13 +; r14 +; (optional padding to qword align rsp) +; callee's r9 +; callee's r8 +; callee's rdx +; callee's rcx + +GenericComCallStub_STACK_FRAME_SIZE = 0 + +; ComMethodFrame MUST be the highest part of the stack frame, immediately +; below the return address and MethodDesc*, so that +; UnmanagedToManagedFrame::m_ReturnAddress and +; UnmanagedToManagedFrame::m_Datum are the right place. +GenericComCallStub_STACK_FRAME_SIZE = GenericComCallStub_STACK_FRAME_SIZE + (SIZEOF__ComMethodFrame - 8) +GenericComCallStub_ComMethodFrame_NEGOFFSET = GenericComCallStub_STACK_FRAME_SIZE + +GenericComCallStub_STACK_FRAME_SIZE = GenericComCallStub_STACK_FRAME_SIZE + SIZEOF_GSCookie + +; Ensure that the offset of the XMM save area will be 16-byte aligned. +if ((GenericComCallStub_STACK_FRAME_SIZE + 8) MOD 16) ne 0 +GenericComCallStub_STACK_FRAME_SIZE = GenericComCallStub_STACK_FRAME_SIZE + 8 +endif + +; XMM save area MUST be immediately below GenericComCallStub +; (w/ alignment padding) +GenericComCallStub_STACK_FRAME_SIZE = GenericComCallStub_STACK_FRAME_SIZE + 4*16 +GenericComCallStub_XMM_SAVE_NEGOFFSET = GenericComCallStub_STACK_FRAME_SIZE + +; Add in the callee scratch area size. +GenericComCallStub_CALLEE_SCRATCH_SIZE = 4*8 +GenericComCallStub_STACK_FRAME_SIZE = GenericComCallStub_STACK_FRAME_SIZE + GenericComCallStub_CALLEE_SCRATCH_SIZE + +; Now we have the full size of the stack frame. The offsets have been computed relative to the +; top, so negate them to make them relative to the post-prologue rsp. +GenericComCallStub_ComMethodFrame_OFFSET = GenericComCallStub_STACK_FRAME_SIZE - GenericComCallStub_ComMethodFrame_NEGOFFSET +GenericComCallStub_XMM_SAVE_OFFSET = GenericComCallStub_STACK_FRAME_SIZE - GenericComCallStub_XMM_SAVE_NEGOFFSET +OFFSETOF_GSCookie = GenericComCallStub_ComMethodFrame_OFFSET - SIZEOF_GSCookie + + .allocstack 8 ; UnmanagedToManagedFrame::m_Datum, pushed by prepad + + ; + ; Allocate the remainder of the ComMethodFrame. The fields + ; will be filled in by COMToCLRWorker + ; + alloc_stack SIZEOF__ComMethodFrame - 10h + + ; + ; Save ComMethodFrame* to pass to COMToCLRWorker + ; + mov r10, rsp + + alloc_stack GenericComCallStub_ComMethodFrame_OFFSET + + ; + ; Save argument registers + ; + SAVE_ARGUMENT_REGISTERS GenericComCallStub_STACK_FRAME_SIZE + 8h + + ; + ; spill the fp args + ; + SAVE_FLOAT_ARGUMENT_REGISTERS GenericComCallStub_XMM_SAVE_OFFSET + + END_PROLOGUE + + mov rcx, s_gsCookie + mov [rsp + OFFSETOF_GSCookie], rcx + + ; + ; Call COMToCLRWorker. Note that the first parameter (pThread) is + ; filled in by callee. + ; + +ifdef _DEBUG + mov rcx, 0cccccccccccccccch +endif + mov rdx, r10 + call COMToCLRWorker + +ifdef _DEBUG + mov rcx, s_gsCookie + cmp [rsp + OFFSETOF_GSCookie], rcx + je GoodGSCookie + call JIT_FailFast +GoodGSCookie: +endif ; _DEBUG + + ; + ; epilogue + ; + add rsp, GenericComCallStub_STACK_FRAME_SIZE + ret + +NESTED_END GenericComCallStub, _TEXT + + +; ARG_SLOT COMToCLRDispatchHelperWithStack(DWORD dwStackSlots, // rcx +; ComMethodFrame *pFrame, // rdx +; PCODE pTarget, // r8 +; PCODE pSecretArg, // r9 +; INT_PTR pDangerousThis // rbp+40h +; ); +NESTED_ENTRY COMToCLRDispatchHelperWithStack, _TEXT, CallDescrWorkerUnwindFrameChainHandler + +ComMethodFrame_Arguments_OFFSET = SIZEOF__ComMethodFrame +ComMethodFrame_XMM_SAVE_OFFSET = GenericComCallStub_XMM_SAVE_OFFSET - GenericComCallStub_ComMethodFrame_OFFSET + + push_nonvol_reg rdi ; save nonvolatile registers + push_nonvol_reg rsi ; + push_nonvol_reg rbp ; + set_frame rbp, 0 ; set frame pointer + + END_PROLOGUE + + + ; + ; copy stack + ; + lea rsi, [rdx + ComMethodFrame_Arguments_OFFSET] + add ecx, 4 ; outgoing argument homes + mov eax, ecx ; number of stack slots + shl eax, 3 ; compute number of argument bytes + add eax, 8h ; alignment padding + and rax, 0FFFFFFFFFFFFFFf0h ; for proper stack alignment, v-liti remove partial register stall + sub rsp, rax ; allocate argument list + mov rdi, rsp ; set destination argument list address + rep movsq ; copy arguments to the stack + + + ; Stack layout: + ; + ; callee's rcx (to be loaded into rcx) <- rbp+40h + ; r9 (to be loaded into r10) + ; r8 (IL stub entry point) + ; rdx (ComMethodFrame ptr) + ; rcx (number of stack slots to repush) + ; return address + ; saved rdi + ; saved rsi + ; saved rbp <- rbp + ; alignment + ; (stack parameters) + ; callee's r9 + ; callee's r8 + ; callee's rdx + ; callee's rcx (not loaded into rcx) <- rsp + + ; + ; load fp registers + ; + movdqa xmm0, [rdx + ComMethodFrame_XMM_SAVE_OFFSET + 00h] + movdqa xmm1, [rdx + ComMethodFrame_XMM_SAVE_OFFSET + 10h] + movdqa xmm2, [rdx + ComMethodFrame_XMM_SAVE_OFFSET + 20h] + movdqa xmm3, [rdx + ComMethodFrame_XMM_SAVE_OFFSET + 30h] + + ; + ; load secret arg and target + ; + mov r10, r9 + mov rax, r8 + + ; + ; load argument registers + ; + mov rcx, [rbp + 40h] ; ignoring the COM IP at [rsp] + mov rdx, [rsp + 08h] + mov r8, [rsp + 10h] + mov r9, [rsp + 18h] + + ; + ; call the target + ; + call rax + + ; It is important to have an instruction between the previous call and the epilog. + ; If the return address is in epilog, OS won't call personality routine because + ; it thinks personality routine does not help in this case. + nop + + ; + ; epilog + ; + lea rsp, 0[rbp] ; deallocate argument list + pop rbp ; restore nonvolatile register + pop rsi ; + pop rdi ; + ret + +NESTED_END COMToCLRDispatchHelperWithStack, _TEXT + +; ARG_SLOT COMToCLRDispatchHelper(DWORD dwStackSlots, // rcx +; ComMethodFrame *pFrame, // rdx +; PCODE pTarget, // r8 +; PCODE pSecretArg, // r9 +; INT_PTR pDangerousThis // rsp + 28h on entry +; ); +NESTED_ENTRY COMToCLRDispatchHelper, _TEXT, CallDescrWorkerUnwindFrameChainHandler + + ; + ; Check to see if we have stack to copy and, if so, tail call to + ; the routine that can handle that. + ; + test ecx, ecx + jnz COMToCLRDispatchHelperWithStack + + alloc_stack 28h ; alloc scratch space + alignment, pDangerousThis moves to [rsp+50] + END_PROLOGUE + + + ; get pointer to arguments + lea r11, [rdx + ComMethodFrame_Arguments_OFFSET] + + ; + ; load fp registers + ; + movdqa xmm0, [rdx + ComMethodFrame_XMM_SAVE_OFFSET + 00h] + movdqa xmm1, [rdx + ComMethodFrame_XMM_SAVE_OFFSET + 10h] + movdqa xmm2, [rdx + ComMethodFrame_XMM_SAVE_OFFSET + 20h] + movdqa xmm3, [rdx + ComMethodFrame_XMM_SAVE_OFFSET + 30h] + + ; + ; load secret arg and target + ; + mov r10, r9 + mov rax, r8 + + ; + ; load argument registers + ; + mov rcx, [rsp + 50h] ; ignoring the COM IP at [r11 + 00h] + mov rdx, [r11 + 08h] + mov r8, [r11 + 10h] + mov r9, [r11 + 18h] + + ; + ; call the target + ; + call rax + + ; It is important to have an instruction between the previous call and the epilog. + ; If the return address is in epilog, OS won't call personality routine because + ; it thinks personality routine does not help in this case. + nop + + ; + ; epilog + ; + add rsp, 28h + ret +NESTED_END COMToCLRDispatchHelper, _TEXT + + + +endif ; FEATURE_COMINTEROP + + end + diff --git a/src/vm/amd64/GenericComPlusCallStubs.asm b/src/vm/amd64/GenericComPlusCallStubs.asm new file mode 100644 index 0000000000..9240ad5fa7 --- /dev/null +++ b/src/vm/amd64/GenericComPlusCallStubs.asm @@ -0,0 +1,148 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; ==++== +; + +; +; ==--== + +ifdef FEATURE_COMINTEROP + +include AsmMacros.inc +include asmconstants.inc + +CTPMethodTable__s_pThunkTable equ ?s_pThunkTable@CTPMethodTable@@0PEAVMethodTable@@EA +InstantiatedMethodDesc__IMD_GetComPlusCallInfo equ ?IMD_GetComPlusCallInfo@InstantiatedMethodDesc@@QEAAPEAUComPlusCallInfo@@XZ + +ifdef FEATURE_REMOTING +extern CRemotingServices__DispatchInterfaceCall:proc +extern CTPMethodTable__s_pThunkTable:qword +extern InstantiatedMethodDesc__IMD_GetComPlusCallInfo:proc +endif + +extern CLRToCOMWorker:proc +extern ProcessCLRException:proc + +ifdef FEATURE_REMOTING +; +; in: +; r10: MethodDesc* +; rcx: 'this' object +; +; out: +; METHODDESC_REGISTER (r10) = MethodDesc* (for IL stubs) +; +LEAF_ENTRY GenericComPlusCallStub, _TEXT + + ; + ; check for a null 'this' pointer and + ; then see if this is a TransparentProxy + ; + + test rcx, rcx + jz do_com_call + + mov rax, [CTPMethodTable__s_pThunkTable] + cmp [rcx], rax + jne do_com_call + + ; + ; 'this' is a TransparentProxy + ; + jmp CRemotingServices__DispatchInterfaceCall + +do_com_call: + + ; + ; Check if the call is being made on an InstantiatedMethodDesc. + ; + + mov ax, [r10 + OFFSETOF__MethodDesc__m_wFlags] + and ax, MethodDescClassification__mdcClassification + cmp ax, MethodDescClassification__mcInstantiated + je GenericComPlusCallWorkerInstantiated + + ; + ; Check if there is an IL stub. + ; + + mov rax, [r10 + OFFSETOF__ComPlusCallMethodDesc__m_pComPlusCallInfo] + mov rax, [rax + OFFSETOF__ComPlusCallInfo__m_pILStub] + test rax, rax + jz GenericComPlusCallStubSlow + + TAILJMP_RAX + +LEAF_END GenericComPlusCallStub, _TEXT + +; We could inline IMD_GetComPlusCallInfo here but it would be ugly. +NESTED_ENTRY GenericComPlusCallWorkerInstantiated, _TEXT, ProcessCLRException + alloc_stack 68h + + save_reg_postrsp r10, 60h + + SAVE_ARGUMENT_REGISTERS 70h + + SAVE_FLOAT_ARGUMENT_REGISTERS 20h + + END_PROLOGUE + + mov rcx, r10 + call InstantiatedMethodDesc__IMD_GetComPlusCallInfo + + RESTORE_FLOAT_ARGUMENT_REGISTERS 20h + + RESTORE_ARGUMENT_REGISTERS 70h + + mov r10, [rsp + 60h] + + mov rax, [rax + OFFSETOF__ComPlusCallInfo__m_pILStub] + + add rsp, 68h + TAILJMP_RAX +NESTED_END GenericComPlusCallWorkerInstantiated, _TEXT +endif + + +ifdef FEATURE_REMOTING +NESTED_ENTRY GenericComPlusCallStubSlow, _TEXT, ProcessCLRException +else +NESTED_ENTRY GenericComPlusCallStub, _TEXT, ProcessCLRException +endif + + PROLOG_WITH_TRANSITION_BLOCK 8 + + ; + ; Call CLRToCOMWorker. + ; + lea rcx, [rsp + __PWTB_TransitionBlock] ; pTransitionBlock + mov rdx, r10 ; MethodDesc * + call CLRToCOMWorker + + ; handle FP return values + + lea rcx, [rsp + __PWTB_FloatArgumentRegisters - 8] + cmp rax, 4 + jne @F + movss xmm0, real4 ptr [rcx] +@@: + cmp rax, 8 + jne @F + movsd xmm0, real8 ptr [rcx] +@@: + ; load return value + mov rax, [rcx] + + EPILOG_WITH_TRANSITION_BLOCK_RETURN + +ifdef FEATURE_REMOTING +NESTED_END GenericComPlusCallStubSlow, _TEXT +else +NESTED_END GenericComPlusCallStub, _TEXT +endif + +endif ; FEATURE_COMINTEROP + + end diff --git a/src/vm/amd64/InstantiatingStub.asm b/src/vm/amd64/InstantiatingStub.asm new file mode 100644 index 0000000000..dff1b6f5a6 --- /dev/null +++ b/src/vm/amd64/InstantiatingStub.asm @@ -0,0 +1,151 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; ==++== +; + +; +; ==--== + +include +include AsmConstants.inc + +extern s_pStubHelperFrameVPtr:qword +extern JIT_FailFast:proc +extern s_gsCookie:qword + + +OFFSETOF_SECRET_PARAMS equ 0h +OFFSETOF_GSCOOKIE equ OFFSETOF_SECRET_PARAMS + \ + 18h + 8h ; +8 for stack alignment padding +OFFSETOF_FRAME equ OFFSETOF_GSCOOKIE + \ + 8h +OFFSETOF_FRAME_REGISTERS equ OFFSETOF_FRAME + \ + SIZEOF__Frame +SIZEOF_FIXED_FRAME equ OFFSETOF_FRAME_REGISTERS + \ + SIZEOF_CalleeSavedRegisters + 8h ; +8 for return address + +.errnz SIZEOF_FIXED_FRAME mod 16, SIZEOF_FIXED_FRAME not aligned + +; +; This method takes three secret parameters on the stack: +; +; incoming: +; +; rsp -> nStackSlots +; entrypoint of shared MethodDesc +; extra stack param +; +; return address +; rcx home +; rdx home +; : +; +; +; Stack Layout: +; +; rsp-> callee scratch +; + 8h callee scratch +; +10h callee scratch +; +18h callee scratch +; : +; stack arguments +; : +; rbp-> nStackSlots +; + 8h entrypoint of shared MethodDesc +; +10h extra stack param +; +18h padding +; +20h gsCookie +; +28h __VFN_table +; +30h m_Next +; +38h m_calleeSavedRegisters +; +98h m_ReturnAddress +; +a0h rcx home +; +a8h rdx home +; +b0h r8 home +; +b8h r9 home +; +NESTED_ENTRY InstantiatingMethodStubWorker, _TEXT + .allocstack SIZEOF_FIXED_FRAME - 8h ; -8 for return address + + SAVE_CALLEE_SAVED_REGISTERS OFFSETOF_FRAME_REGISTERS + + SAVE_ARGUMENT_REGISTERS SIZEOF_FIXED_FRAME + + set_frame rbp, 0 + END_PROLOGUE + + sub rsp, SIZEOF_MAX_OUTGOING_ARGUMENT_HOMES + + ; + ; fully initialize the StubHelperFrame + ; + mov rax, s_pStubHelperFrameVPtr + mov [rbp + OFFSETOF_FRAME], rax + + mov rax, s_gsCookie + mov [rbp + OFFSETOF_GSCOOKIE], rax + + ; + ; link the StubHelperFrame + ; + CALL_GETTHREAD + mov rdx, [rax + OFFSETOF__Thread__m_pFrame] + mov [rbp + OFFSETOF_FRAME + OFFSETOF__Frame__m_Next], rdx + lea rcx, [rbp + OFFSETOF_FRAME] + mov [rax + OFFSETOF__Thread__m_pFrame], rcx + + mov r12, rax ; store the Thread pointer + + add rsp, SIZEOF_MAX_OUTGOING_ARGUMENT_HOMES + + mov rcx, [rbp + OFFSETOF_SECRET_PARAMS + 0h] ; nStackSlots (includes padding for stack alignment) + + lea rsi, [rbp + SIZEOF_FIXED_FRAME + SIZEOF_MAX_OUTGOING_ARGUMENT_HOMES + 8 * rcx] + +StackCopyLoop: ; copy the arguments to stack top-down to carefully probe for sufficient stack space + sub rsi, 8 + push qword ptr [rsi] + dec rcx + jnz StackCopyLoop + + push qword ptr [rbp+OFFSETOF_SECRET_PARAMS + 10h] ; push extra stack arg + sub rsp, SIZEOF_MAX_OUTGOING_ARGUMENT_HOMES + + mov rcx, [rbp + SIZEOF_FIXED_FRAME + 00h] + mov rdx, [rbp + SIZEOF_FIXED_FRAME + 08h] + mov r8, [rbp + SIZEOF_FIXED_FRAME + 10h] + mov r9, [rbp + SIZEOF_FIXED_FRAME + 18h] + + call qword ptr [rbp+OFFSETOF_SECRET_PARAMS + 8h] ; call target + +ifdef _DEBUG + mov rcx, s_gsCookie + cmp [rbp + OFFSETOF_GSCookie], rcx + je GoodGSCookie + call JIT_FailFast +GoodGSCookie: +endif ; _DEBUG + + ; + ; unlink the StubHelperFrame + ; + mov rcx, [rbp + OFFSETOF_FRAME + OFFSETOF__Frame__m_Next] + mov [r12 + OFFSETOF__Thread__m_pFrame], rcx + + ; + ; epilog + ; + + lea rsp, [rbp + OFFSETOF_FRAME_REGISTERS] + + POP_CALLEE_SAVED_REGISTERS + + ret + +NESTED_END InstantiatingMethodStubWorker, _TEXT + + + end + diff --git a/src/vm/amd64/JitHelpers_Fast.asm b/src/vm/amd64/JitHelpers_Fast.asm new file mode 100644 index 0000000000..f004be549e --- /dev/null +++ b/src/vm/amd64/JitHelpers_Fast.asm @@ -0,0 +1,1028 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; ==++== +; + +; +; ==--== +; *********************************************************************** +; File: JitHelpers_Fast.asm, see jithelp.asm for history +; +; Notes: routinues which we believe to be on the hot path for managed +; code in most scenarios. +; *********************************************************************** + + +include AsmMacros.inc +include asmconstants.inc + +; Min amount of stack space that a nested function should allocate. +MIN_SIZE equ 28h + +EXTERN g_ephemeral_low:QWORD +EXTERN g_ephemeral_high:QWORD +EXTERN g_lowest_address:QWORD +EXTERN g_highest_address:QWORD +EXTERN g_card_table:QWORD + +ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP +EXTERN g_sw_ww_table:QWORD +EXTERN g_sw_ww_enabled_for_gc_heap:BYTE +endif + +ifdef WRITE_BARRIER_CHECK +; Those global variables are always defined, but should be 0 for Server GC +g_GCShadow TEXTEQU +g_GCShadowEnd TEXTEQU +EXTERN g_GCShadow:QWORD +EXTERN g_GCShadowEnd:QWORD +endif + +INVALIDGCVALUE equ 0CCCCCCCDh + +ifdef _DEBUG +extern JIT_WriteBarrier_Debug:proc +endif + +extern JIT_InternalThrow:proc + +extern JITutil_ChkCastInterface:proc +extern JITutil_IsInstanceOfInterface:proc +extern JITutil_ChkCastAny:proc +extern JITutil_IsInstanceOfAny:proc + +;EXTERN_C Object* JIT_IsInstanceOfClass(MethodTable* pMT, Object* pObject); +LEAF_ENTRY JIT_IsInstanceOfClass, _TEXT + ; move rdx into rax in case of a match or null + mov rax, rdx + + ; check if the instance is null + test rdx, rdx + je IsNullInst + + ; check is the MethodTable for the instance matches pMT + cmp rcx, qword ptr [rdx] + jne JIT_IsInstanceOfClass2 + + IsNullInst: + REPRET +LEAF_END JIT_IsInstanceOfClass, _TEXT + +LEAF_ENTRY JIT_IsInstanceOfClass2, _TEXT + ; check if the parent class matches. + ; start by putting the MethodTable for the instance in rdx + mov rdx, qword ptr [rdx] + + align 16 + CheckParent: + ; NULL parent MethodTable* indicates that we're at the top of the hierarchy + + ; unroll 0 + mov rdx, qword ptr [rdx + OFFSETOF__MethodTable__m_pParentMethodTable] + cmp rcx, rdx + je IsInst + test rdx, rdx + je DoneWithLoop + + ; unroll 1 + mov rdx, qword ptr [rdx + OFFSETOF__MethodTable__m_pParentMethodTable] + cmp rcx, rdx + je IsInst + test rdx, rdx + je DoneWithLoop + + ; unroll 2 + mov rdx, qword ptr [rdx + OFFSETOF__MethodTable__m_pParentMethodTable] + cmp rcx, rdx + je IsInst + test rdx, rdx + je DoneWithLoop + + ; unroll 3 + mov rdx, qword ptr [rdx + OFFSETOF__MethodTable__m_pParentMethodTable] + cmp rcx, rdx + je IsInst + test rdx, rdx + jne CheckParent + + align 16 + DoneWithLoop: +if METHODTABLE_EQUIVALENCE_FLAGS gt 0 + ; check if the instance is a proxy or has type equivalence + ; get the MethodTable of the original Object (stored earlier in rax) + mov rdx, [rax] + test dword ptr [rdx + OFFSETOF__MethodTable__m_dwFlags], METHODTABLE_EQUIVALENCE_FLAGS + jne SlowPath +endif ; METHODTABLE_EQUIVALENCE_FLAGS gt 0 + + ; we didn't find a match in the ParentMethodTable hierarchy + ; and it isn't a proxy and doesn't have type equivalence, return NULL + xor eax, eax + ret +if METHODTABLE_EQUIVALENCE_FLAGS gt 0 + SlowPath: + ; Set up the args to call JITutil_IsInstanceOfAny. Note that rcx already contains + ; the MethodTable* + mov rdx, rax ; rdx = Object* + + ; Call out to JITutil_IsInstanceOfAny to handle the proxy/equivalence case. + jmp JITutil_IsInstanceOfAny +endif ; METHODTABLE_EQUIVALENCE_FLAGS gt 0 + ; if it is a null instance then rax is null + ; if they match then rax contains the instance + align 16 + IsInst: + REPRET +LEAF_END JIT_IsInstanceOfClass2, _TEXT + +; TODO: this is not necessary... we will be calling JIT_ChkCastClass2 all of the time +; now that the JIT inlines the null check and the exact MT comparison... Or are +; they only doing it on the IBC hot path??? Look into that. If it will turn out +; to be cold then put it down at the bottom. + +;EXTERN_C Object* JIT_ChkCastClass(MethodTable* pMT, Object* pObject); +LEAF_ENTRY JIT_ChkCastClass, _TEXT + ; check if the instance is null + test rdx, rdx + je IsNullInst + + ; check if the MethodTable for the instance matches pMT + cmp rcx, qword ptr [rdx] + jne JIT_ChkCastClassSpecial + + IsNullInst: + ; setup the return value for a match or null + mov rax, rdx + ret +LEAF_END JIT_ChkCastClass, _TEXT + +LEAF_ENTRY JIT_ChkCastClassSpecial, _TEXT + ; save off the instance in case it is a proxy, and to setup + ; our return value for a match + mov rax, rdx + + ; check if the parent class matches. + ; start by putting the MethodTable for the instance in rdx + mov rdx, qword ptr [rdx] + align 16 + CheckParent: + ; NULL parent MethodTable* indicates that we're at the top of the hierarchy + + ; unroll 0 + mov rdx, qword ptr [rdx + OFFSETOF__MethodTable__m_pParentMethodTable] + cmp rcx, rdx + je IsInst + test rdx, rdx + je DoneWithLoop + + ; unroll 1 + mov rdx, qword ptr [rdx + OFFSETOF__MethodTable__m_pParentMethodTable] + cmp rcx, rdx + je IsInst + test rdx, rdx + je DoneWithLoop + + ; unroll 2 + mov rdx, qword ptr [rdx + OFFSETOF__MethodTable__m_pParentMethodTable] + cmp rcx, rdx + je IsInst + test rdx, rdx + je DoneWithLoop + + ; unroll 3 + mov rdx, qword ptr [rdx + OFFSETOF__MethodTable__m_pParentMethodTable] + cmp rcx, rdx + je IsInst + test rdx, rdx + jne CheckParent + + align 16 + DoneWithLoop: + ; Set up the args to call JITutil_ChkCastAny. Note that rcx already contains the MethodTable* + mov rdx, rax ; rdx = Object* + + ; Call out to JITutil_ChkCastAny to handle the proxy case and throw a rich + ; InvalidCastException in case of failure. + jmp JITutil_ChkCastAny + + ; if it is a null instance then rax is null + ; if they match then rax contains the instance + align 16 + IsInst: + REPRET +LEAF_END JIT_ChkCastClassSpecial, _TEXT + +FIX_INDIRECTION macro Reg +ifdef FEATURE_PREJIT + test Reg, 1 + jz @F + mov Reg, [Reg-1] + @@: +endif +endm + +; PERF TODO: consider prefetching the entire interface map into the cache + +; For all bizarre castes this quickly fails and falls back onto the JITutil_IsInstanceOfAny +; helper, this means that all failure cases take the slow path as well. +; +; This can trash r10/r11 +LEAF_ENTRY JIT_IsInstanceOfInterface, _TEXT + test rdx, rdx + jz IsNullInst + + ; get methodtable + mov rax, [rdx] + mov r11w, word ptr [rax + OFFSETOF__MethodTable__m_wNumInterfaces] + + test r11w, r11w + jz DoBizarre + + ; fetch interface map ptr + mov rax, [rax + OFFSETOF__MethodTable__m_pInterfaceMap] + + ; r11 holds number of interfaces + ; rax is pointer to beginning of interface map list + align 16 + Top: + ; rax -> InterfaceInfo_t* into the interface map, aligned to 4 entries + ; use offsets of SIZEOF__InterfaceInfo_t to get at entry 1, 2, 3 in this + ; block. If we make it through the full 4 without a hit we'll move to + ; the next block of 4 and try again. + + ; unroll 0 +ifdef FEATURE_PREJIT + mov r10, [rax + OFFSETOF__InterfaceInfo_t__m_pMethodTable] + FIX_INDIRECTION r10 + cmp rcx, r10 +else + cmp rcx, [rax + OFFSETOF__InterfaceInfo_t__m_pMethodTable] +endif + je Found + ; move to next entry in list + dec r11w + jz DoBizarre + + ; unroll 1 +ifdef FEATURE_PREJIT + mov r10, [rax + SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] + FIX_INDIRECTION r10 + cmp rcx, r10 +else + cmp rcx, [rax + SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] +endif + je Found + ; move to next entry in list + dec r11w + jz DoBizarre + + ; unroll 2 +ifdef FEATURE_PREJIT + mov r10, [rax + 2 * SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] + FIX_INDIRECTION r10 + cmp rcx, r10 +else + cmp rcx, [rax + 2 * SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] +endif + je Found + ; move to next entry in list + dec r11w + jz DoBizarre + + ; unroll 3 +ifdef FEATURE_PREJIT + mov r10, [rax + 3 * SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] + FIX_INDIRECTION r10 + cmp rcx, r10 +else + cmp rcx, [rax + 3 * SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] +endif + je Found + ; move to next entry in list + dec r11w + jz DoBizarre + + ; if we didn't find the entry in this loop jump to the next 4 entries in the map + add rax, 4 * SIZEOF__InterfaceInfo_t + jmp Top + + DoBizarre: + mov rax, [rdx] + test dword ptr [rax + OFFSETOF__MethodTable__m_dwFlags], METHODTABLE_NONTRIVIALINTERFACECAST_FLAGS + jnz NonTrivialCast + xor rax,rax + ret + + align 16 + Found: + IsNullInst: + ; return the successful instance + mov rax, rdx + ret + + NonTrivialCast: + jmp JITutil_IsInstanceOfInterface +LEAF_END JIT_IsInstanceOfInterface, _TEXT + +; For all bizarre castes this quickly fails and falls back onto the JITutil_ChkCastInterface +; helper, this means that all failure cases take the slow path as well. +; +; This can trash r10/r11 +LEAF_ENTRY JIT_ChkCastInterface, _TEXT + test rdx, rdx + jz IsNullInst + + ; get methodtable + mov rax, [rdx] + mov r11w, word ptr [rax + OFFSETOF__MethodTable__m_wNumInterfaces] + + ; speculatively fetch interface map ptr + mov rax, [rax + OFFSETOF__MethodTable__m_pInterfaceMap] + + test r11w, r11w + jz DoBizarre + + ; r11 holds number of interfaces + ; rax is pointer to beginning of interface map list + align 16 + Top: + ; rax -> InterfaceInfo_t* into the interface map, aligned to 4 entries + ; use offsets of SIZEOF__InterfaceInfo_t to get at entry 1, 2, 3 in this + ; block. If we make it through the full 4 without a hit we'll move to + ; the next block of 4 and try again. + + ; unroll 0 +ifdef FEATURE_PREJIT + mov r10, [rax + OFFSETOF__InterfaceInfo_t__m_pMethodTable] + FIX_INDIRECTION r10 + cmp rcx, r10 +else + cmp rcx, [rax + OFFSETOF__InterfaceInfo_t__m_pMethodTable] +endif + je Found + ; move to next entry in list + dec r11w + jz DoBizarre + + ; unroll 1 +ifdef FEATURE_PREJIT + mov r10, [rax + SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] + FIX_INDIRECTION r10 + cmp rcx, r10 +else + cmp rcx, [rax + SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] +endif + je Found + ; move to next entry in list + dec r11w + jz DoBizarre + + ; unroll 2 +ifdef FEATURE_PREJIT + mov r10, [rax + 2 * SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] + FIX_INDIRECTION r10 + cmp rcx, r10 +else + cmp rcx, [rax + 2 * SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] +endif + je Found + ; move to next entry in list + dec r11w + jz DoBizarre + + ; unroll 3 +ifdef FEATURE_PREJIT + mov r10, [rax + 3 * SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] + FIX_INDIRECTION r10 + cmp rcx, r10 +else + cmp rcx, [rax + 3 * SIZEOF__InterfaceInfo_t + OFFSETOF__InterfaceInfo_t__m_pMethodTable] +endif + je Found + ; move to next entry in list + dec r11w + jz DoBizarre + + ; if we didn't find the entry in this loop jump to the next 4 entries in the map + add rax, 4 * SIZEOF__InterfaceInfo_t + jmp Top + + DoBizarre: + jmp JITutil_ChkCastInterface + + align 16 + Found: + IsNullInst: + ; return either NULL or the successful instance + mov rax, rdx + ret +LEAF_END JIT_ChkCastInterface, _TEXT + +; There is an even more optimized version of these helpers possible which takes +; advantage of knowledge of which way the ephemeral heap is growing to only do 1/2 +; that check (this is more significant in the JIT_WriteBarrier case). +; +; Additionally we can look into providing helpers which will take the src/dest from +; specific registers (like x86) which _could_ (??) make for easier register allocation +; for the JIT64, however it might lead to having to have some nasty code that treats +; these guys really special like... :(. +; +; Version that does the move, checks whether or not it's in the GC and whether or not +; it needs to have it's card updated +; +; void JIT_CheckedWriteBarrier(Object** dst, Object* src) +LEAF_ENTRY JIT_CheckedWriteBarrier, _TEXT + + ; When WRITE_BARRIER_CHECK is defined _NotInHeap will write the reference + ; but if it isn't then it will just return. + ; + ; See if this is in GCHeap + cmp rcx, [g_lowest_address] + jb NotInHeap + cmp rcx, [g_highest_address] + jnb NotInHeap + + jmp JIT_WriteBarrier + + NotInHeap: + ; See comment above about possible AV + mov [rcx], rdx + ret +LEAF_END_MARKED JIT_CheckedWriteBarrier, _TEXT + +; Mark start of the code region that we patch at runtime +LEAF_ENTRY JIT_PatchedCodeStart, _TEXT + ret +LEAF_END JIT_PatchedCodeStart, _TEXT + + +; This is used by the mechanism to hold either the JIT_WriteBarrier_PreGrow +; or JIT_WriteBarrier_PostGrow code (depending on the state of the GC). It _WILL_ +; change at runtime as the GC changes. Initially it should simply be a copy of the +; larger of the two functions (JIT_WriteBarrier_PostGrow) to ensure we have created +; enough space to copy that code in. +LEAF_ENTRY JIT_WriteBarrier, _TEXT + align 16 + +ifdef _DEBUG + ; In debug builds, this just contains jump to the debug version of the write barrier by default + jmp JIT_WriteBarrier_Debug +endif + +ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + ; JIT_WriteBarrier_WriteWatch_PostGrow64 + + ; Regarding patchable constants: + ; - 64-bit constants have to be loaded into a register + ; - The constants have to be aligned to 8 bytes so that they can be patched easily + ; - The constant loads have been located to minimize NOP padding required to align the constants + ; - Using different registers for successive constant loads helps pipeline better. Should we decide to use a special + ; non-volatile calling convention, this should be changed to use just one register. + + ; Do the move into the GC . It is correct to take an AV here, the EH code + ; figures out that this came from a WriteBarrier and correctly maps it back + ; to the managed method which called the WriteBarrier (see setup in + ; InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rcx], rdx + + ; Update the write watch table if necessary + mov rax, rcx + mov r8, 0F0F0F0F0F0F0F0F0h + shr rax, 0Ch ; SoftwareWriteWatch::AddressToTableByteIndexShift + NOP_2_BYTE ; padding for alignment of constant + mov r9, 0F0F0F0F0F0F0F0F0h + add rax, r8 + cmp byte ptr [rax], 0h + jne CheckCardTable + mov byte ptr [rax], 0FFh + + NOP_3_BYTE ; padding for alignment of constant + + ; Check the lower and upper ephemeral region bounds + CheckCardTable: + cmp rdx, r9 + jb Exit + + NOP_3_BYTE ; padding for alignment of constant + + mov r8, 0F0F0F0F0F0F0F0F0h + + cmp rdx, r8 + jae Exit + + nop ; padding for alignment of constant + + mov rax, 0F0F0F0F0F0F0F0F0h + + ; Touch the card table entry, if not already dirty. + shr rcx, 0Bh + cmp byte ptr [rcx + rax], 0FFh + jne UpdateCardTable + REPRET + + UpdateCardTable: + mov byte ptr [rcx + rax], 0FFh + ret + + align 16 + Exit: + REPRET +else + ; JIT_WriteBarrier_PostGrow64 + + ; Do the move into the GC . It is correct to take an AV here, the EH code + ; figures out that this came from a WriteBarrier and correctly maps it back + ; to the managed method which called the WriteBarrier (see setup in + ; InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rcx], rdx + + NOP_3_BYTE ; padding for alignment of constant + + ; Can't compare a 64 bit immediate, so we have to move them into a + ; register. Values of these immediates will be patched at runtime. + ; By using two registers we can pipeline better. Should we decide to use + ; a special non-volatile calling convention, this should be changed to + ; just one. + + mov rax, 0F0F0F0F0F0F0F0F0h + + ; Check the lower and upper ephemeral region bounds + cmp rdx, rax + jb Exit + + nop ; padding for alignment of constant + + mov r8, 0F0F0F0F0F0F0F0F0h + + cmp rdx, r8 + jae Exit + + nop ; padding for alignment of constant + + mov rax, 0F0F0F0F0F0F0F0F0h + + ; Touch the card table entry, if not already dirty. + shr rcx, 0Bh + cmp byte ptr [rcx + rax], 0FFh + jne UpdateCardTable + REPRET + + UpdateCardTable: + mov byte ptr [rcx + rax], 0FFh + ret + + align 16 + Exit: + REPRET +endif + + ; make sure this guy is bigger than any of the other guys + align 16 + nop +LEAF_END_MARKED JIT_WriteBarrier, _TEXT + +ifndef FEATURE_IMPLICIT_TLS +LEAF_ENTRY GetThread, _TEXT + ; the default implementation will just jump to one that returns null until + ; MakeOptimizedTlsGetter is run which will overwrite this with the actual + ; implementation. + jmp short GetTLSDummy + + ; + ; insert enough NOPS to be able to insert the largest optimized TLS getter + ; that we might need, it is important that the TLS getter doesn't overwrite + ; into the dummy getter. + ; + db (TLS_GETTER_MAX_SIZE_ASM - 2) DUP (0CCh) + +LEAF_END GetThread, _TEXT + +LEAF_ENTRY GetAppDomain, _TEXT + ; the default implementation will just jump to one that returns null until + ; MakeOptimizedTlsGetter is run which will overwrite this with the actual + ; implementation. + jmp short GetTLSDummy + + ; + ; insert enough NOPS to be able to insert the largest optimized TLS getter + ; that we might need, it is important that the TLS getter doesn't overwrite + ; into the dummy getter. + ; + db (TLS_GETTER_MAX_SIZE_ASM - 2) DUP (0CCh) + +LEAF_END GetAppDomain, _TEXT + +LEAF_ENTRY GetTLSDummy, _TEXT + xor rax, rax + ret +LEAF_END GetTLSDummy, _TEXT + +LEAF_ENTRY ClrFlsGetBlock, _TEXT + ; the default implementation will just jump to one that returns null until + ; MakeOptimizedTlsGetter is run which will overwrite this with the actual + ; implementation. + jmp short GetTLSDummy + + ; + ; insert enough NOPS to be able to insert the largest optimized TLS getter + ; that we might need, it is important that the TLS getter doesn't overwrite + ; into the dummy getter. + ; + db (TLS_GETTER_MAX_SIZE_ASM - 2) DUP (0CCh) + +LEAF_END ClrFlsGetBlock, _TEXT +endif + +; Mark start of the code region that we patch at runtime +LEAF_ENTRY JIT_PatchedCodeLast, _TEXT + ret +LEAF_END JIT_PatchedCodeLast, _TEXT + +; JIT_ByRefWriteBarrier has weird symantics, see usage in StubLinkerX86.cpp +; +; Entry: +; RDI - address of ref-field (assigned to) +; RSI - address of the data (source) +; RCX is trashed +; RAX is trashed when FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP is defined +; Exit: +; RDI, RSI are incremented by SIZEOF(LPVOID) +LEAF_ENTRY JIT_ByRefWriteBarrier, _TEXT + mov rcx, [rsi] + +; If !WRITE_BARRIER_CHECK do the write first, otherwise we might have to do some ShadowGC stuff +ifndef WRITE_BARRIER_CHECK + ; rcx is [rsi] + mov [rdi], rcx +endif + + ; When WRITE_BARRIER_CHECK is defined _NotInHeap will write the reference + ; but if it isn't then it will just return. + ; + ; See if this is in GCHeap + cmp rdi, [g_lowest_address] + jb NotInHeap + cmp rdi, [g_highest_address] + jnb NotInHeap + +ifdef WRITE_BARRIER_CHECK + ; we can only trash rcx in this function so in _DEBUG we need to save + ; some scratch registers. + push r10 + push r11 + push rax + + ; **ALSO update the shadow GC heap if that is enabled** + ; Do not perform the work if g_GCShadow is 0 + cmp g_GCShadow, 0 + je NoShadow + + ; If we end up outside of the heap don't corrupt random memory + mov r10, rdi + sub r10, [g_lowest_address] + jb NoShadow + + ; Check that our adjusted destination is somewhere in the shadow gc + add r10, [g_GCShadow] + cmp r10, [g_GCShadowEnd] + ja NoShadow + + ; Write ref into real GC + mov [rdi], rcx + ; Write ref into shadow GC + mov [r10], rcx + + ; Ensure that the write to the shadow heap occurs before the read from + ; the GC heap so that race conditions are caught by INVALIDGCVALUE + mfence + + ; Check that GC/ShadowGC values match + mov r11, [rdi] + mov rax, [r10] + cmp rax, r11 + je DoneShadow + mov r11, INVALIDGCVALUE + mov [r10], r11 + + jmp DoneShadow + + ; If we don't have a shadow GC we won't have done the write yet + NoShadow: + mov [rdi], rcx + + ; If we had a shadow GC then we already wrote to the real GC at the same time + ; as the shadow GC so we want to jump over the real write immediately above. + ; Additionally we know for sure that we are inside the heap and therefore don't + ; need to replicate the above checks. + DoneShadow: + pop rax + pop r11 + pop r10 +endif + +ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + ; Update the write watch table if necessary + cmp byte ptr [g_sw_ww_enabled_for_gc_heap], 0h + je CheckCardTable + mov rax, rdi + shr rax, 0Ch ; SoftwareWriteWatch::AddressToTableByteIndexShift + add rax, qword ptr [g_sw_ww_table] + cmp byte ptr [rax], 0h + jne CheckCardTable + mov byte ptr [rax], 0FFh +endif + + ; See if we can just quick out + CheckCardTable: + cmp rcx, [g_ephemeral_low] + jb Exit + cmp rcx, [g_ephemeral_high] + jnb Exit + + ; move current rdi value into rcx and then increment the pointers + mov rcx, rdi + add rsi, 8h + add rdi, 8h + + ; Check if we need to update the card table + ; Calc pCardByte + shr rcx, 0Bh + add rcx, [g_card_table] + + ; Check if this card is dirty + cmp byte ptr [rcx], 0FFh + jne UpdateCardTable + REPRET + + UpdateCardTable: + mov byte ptr [rcx], 0FFh + ret + + align 16 + NotInHeap: +; If WRITE_BARRIER_CHECK then we won't have already done the mov and should do it here +; If !WRITE_BARRIER_CHECK we want _NotInHeap and _Leave to be the same and have both +; 16 byte aligned. +ifdef WRITE_BARRIER_CHECK + ; rcx is [rsi] + mov [rdi], rcx +endif + Exit: + ; Increment the pointers before leaving + add rdi, 8h + add rsi, 8h + ret +LEAF_END JIT_ByRefWriteBarrier, _TEXT + + +g_pObjectClass equ ?g_pObjectClass@@3PEAVMethodTable@@EA + +EXTERN g_pObjectClass:qword +extern ArrayStoreCheck:proc +extern ObjIsInstanceOfNoGC:proc + +; TODO: put definition for this in asmconstants.h +CanCast equ 1 + +;__declspec(naked) void F_CALL_CONV JIT_Stelem_Ref(PtrArray* array, unsigned idx, Object* val) +LEAF_ENTRY JIT_Stelem_Ref, _TEXT + ; check for null PtrArray* + test rcx, rcx + je ThrowNullReferenceException + + ; we only want the lower 32-bits of edx, it might be dirty + or edx, edx + + ; check that index is in bounds + cmp edx, dword ptr [rcx + OFFSETOF__PtrArray__m_NumComponents] ; 8h -> array size offset + jae ThrowIndexOutOfRangeException + + ; r10 = Array MT + mov r10, [rcx] + + ; if we're assigning a null object* then we don't need a write barrier + test r8, r8 + jz AssigningNull + +ifdef CHECK_APP_DOMAIN_LEAKS + ; get Array TypeHandle + mov r9, [r10 + OFFSETOF__MethodTable__m_ElementType] ; 10h -> typehandle offset + ; check for non-MT + test r9, 2 + jnz NoCheck + + ; Check VMflags of element type + mov r9, [r9 + OFFSETOF__MethodTable__m_pEEClass] + mov r9d, dword ptr [r9 + OFFSETOF__EEClass__m_wAuxFlags] + test r9d, EEClassFlags + jnz ArrayStoreCheck_Helper + + NoCheck: +endif + + mov r9, [r10 + OFFSETOF__MethodTable__m_ElementType] ; 10h -> typehandle offset + + ; check for exact match + cmp r9, [r8] + jne NotExactMatch + + DoWrite: + lea rcx, [rcx + 8*rdx + OFFSETOF__PtrArray__m_Array] + mov rdx, r8 + + ; JIT_WriteBarrier(Object** dst, Object* src) + jmp JIT_WriteBarrier + + AssigningNull: + ; write barrier is not needed for assignment of NULL references + mov [rcx + 8*rdx + OFFSETOF__PtrArray__m_Array], r8 + ret + + NotExactMatch: + cmp r9, [g_pObjectClass] + je DoWrite + + jmp JIT_Stelem_Ref__ObjIsInstanceOfNoGC_Helper + + ThrowNullReferenceException: + mov rcx, CORINFO_NullReferenceException_ASM + jmp JIT_InternalThrow + + ThrowIndexOutOfRangeException: + mov rcx, CORINFO_IndexOutOfRangeException_ASM + jmp JIT_InternalThrow +LEAF_END JIT_Stelem_Ref, _TEXT + +NESTED_ENTRY JIT_Stelem_Ref__ObjIsInstanceOfNoGC_Helper, _TEXT + alloc_stack MIN_SIZE + save_reg_postrsp rcx, MIN_SIZE + 8h + save_reg_postrsp rdx, MIN_SIZE + 10h + save_reg_postrsp r8, MIN_SIZE + 18h + END_PROLOGUE + + ; need to get TypeHandle before setting rcx to be the Obj* because that trashes the PtrArray* + mov rdx, r9 + mov rcx, r8 + + ; TypeHandle::CastResult ObjIsInstanceOfNoGC(Object *pElement, TypeHandle toTypeHnd) + call ObjIsInstanceOfNoGC + + mov rcx, [rsp + MIN_SIZE + 8h] + mov rdx, [rsp + MIN_SIZE + 10h] + mov r8, [rsp + MIN_SIZE + 18h] + + cmp eax, CanCast + jne NeedCheck + + lea rcx, [rcx + 8*rdx + OFFSETOF__PtrArray__m_Array] + mov rdx, r8 + add rsp, MIN_SIZE + + ; JIT_WriteBarrier(Object** dst, Object* src) + jmp JIT_WriteBarrier + + NeedCheck: + add rsp, MIN_SIZE + jmp JIT_Stelem_Ref__ArrayStoreCheck_Helper +NESTED_END JIT_Stelem_Ref__ObjIsInstanceOfNoGC_Helper, _TEXT + +; Need to save r8 to provide a stack address for the Object* +NESTED_ENTRY JIT_Stelem_Ref__ArrayStoreCheck_Helper, _TEXT + alloc_stack MIN_SIZE + save_reg_postrsp rcx, MIN_SIZE + 8h + save_reg_postrsp rdx, MIN_SIZE + 10h + save_reg_postrsp r8, MIN_SIZE + 18h + END_PROLOGUE + + lea rcx, [rsp + MIN_SIZE + 18h] + lea rdx, [rsp + MIN_SIZE + 8h] + + ; HCIMPL2(FC_INNER_RET, ArrayStoreCheck, Object** pElement, PtrArray** pArray) + call ArrayStoreCheck + + mov rcx, [rsp + MIN_SIZE + 8h] + mov rdx, [rsp + MIN_SIZE + 10h] + mov r8, [rsp + MIN_SIZE + 18h] + + lea rcx, [rcx + 8*rdx + OFFSETOF__PtrArray__m_Array] + mov rdx, r8 + add rsp, MIN_SIZE + + ; JIT_WriteBarrier(Object** dst, Object* src) + jmp JIT_WriteBarrier + +NESTED_END JIT_Stelem_Ref__ArrayStoreCheck_Helper, _TEXT + + +extern JIT_FailFast:proc +extern s_gsCookie:qword + +OFFSETOF_GSCOOKIE equ 0h +OFFSETOF_FRAME equ OFFSETOF_GSCOOKIE + \ + 8h + +; +; incoming: +; +; rsp -> return address +; : +; +; Stack Layout: +; +; rsp-> callee scratch +; + 8h callee scratch +; +10h callee scratch +; +18h callee scratch +; : +; stack arguments +; : +; r13-> gsCookie +; + 8h __VFN_table +; +10h m_Next +; +18h m_pGCLayout +; +20h m_padding +; +28h m_rdi +; +30h m_rsi +; +38h m_rbx +; +40h m_rbp +; +48h m_r12 +; +50h m_r13 +; +58h m_r14 +; +60h m_r15 +; +68h m_ReturnAddress +; r12 -> // Caller's SP +; +; r14 = GetThread(); +; r15 = GetThread()->GetFrame(); // For restoring/popping the frame +; +NESTED_ENTRY TailCallHelperStub, _TEXT + PUSH_CALLEE_SAVED_REGISTERS + + alloc_stack 48h ; m_padding, m_pGCLayout, m_Next, __VFN_table, gsCookie, outgoing shadow area + + set_frame r13, 20h + END_PROLOGUE + + ; + ; This part is never executed, but we keep it here for reference + ; + int 3 + +if 0 ne 0 + ; Save the caller's SP + mov r12, rsp + ... + + ; + ; fully initialize the TailCallFrame + ; + call TCF_GETMETHODFRAMEVPTR + mov [r13 + OFFSETOF_FRAME], rax + + mov rax, s_gsCookie + mov [r13 + OFFSETOF_GSCOOKIE], rax + + ; + ; link the TailCallFrame + ; + CALL_GETTHREAD + mov r14, rax + mov r15, [rax + OFFSETOF__Thread__m_pFrame] + mov [r13 + OFFSETOF_FRAME + OFFSETOF__Frame__m_Next], r15 + lea r10, [r13 + OFFSETOF_FRAME] + mov [rax + OFFSETOF__Thread__m_pFrame], r10 +endif + + ; the pretend call would be here + ; with the return address pointing this this real epilog + +PATCH_LABEL JIT_TailCallHelperStub_ReturnAddress + + ; our epilog (which also unlinks the TailCallFrame) + +ifdef _DEBUG + mov rcx, s_gsCookie + cmp [r13 + OFFSETOF_GSCookie], rcx + je GoodGSCookie + call JIT_FailFast +GoodGSCookie: +endif ; _DEBUG + + ; + ; unlink the TailCallFrame + ; + mov [r14 + OFFSETOF__Thread__m_pFrame], r15 + + ; + ; epilog + ; + + lea rsp, [r13 + 28h] + POP_CALLEE_SAVED_REGISTERS + ret + +NESTED_END TailCallHelperStub, _TEXT + + end + diff --git a/src/vm/amd64/JitHelpers_FastWriteBarriers.asm b/src/vm/amd64/JitHelpers_FastWriteBarriers.asm new file mode 100644 index 0000000000..07e985f94f --- /dev/null +++ b/src/vm/amd64/JitHelpers_FastWriteBarriers.asm @@ -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. + +; ==++== +; + +; +; ==--== +; *********************************************************************** +; File: JitHelpers_FastWriteBarriers.asm, see jithelp.asm for history +; +; Notes: these are the fast write barriers which are copied in to the +; JIT_WriteBarrier buffer (found in JitHelpers_Fast.asm). +; This code should never be executed at runtime and should end +; up effectively being treated as data. +; *********************************************************************** + +include AsmMacros.inc +include asmconstants.inc + + +; Two super fast helpers that together do the work of JIT_WriteBarrier. These +; use inlined ephemeral region bounds and an inlined pointer to the card table. +; +; Until the GC does some major reshuffling, the ephemeral region will always be +; at the top of the heap, so given that we know the reference is inside the +; heap, we don't have to check against the upper bound of the ephemeral region +; (PreGrow version). Once the GC moves the ephemeral region, this will no longer +; be valid, so we use the PostGrow version to check both the upper and lower +; bounds. The inlined bounds and card table pointers have to be patched +; whenever they change. +; +; At anyone time, the memory pointed to by JIT_WriteBarrier will contain one +; of these functions. See StompWriteBarrierResize and StompWriteBarrierEphemeral +; in VM\AMD64\JITInterfaceAMD64.cpp and InitJITHelpers1 in VM\JITInterfaceGen.cpp +; for more info. +; +; READ THIS!!!!!! +; it is imperative that the addresses of of the values that we overwrite +; (card table, ephemeral region ranges, etc) are naturally aligned since +; there are codepaths that will overwrite these values while the EE is running. +; +LEAF_ENTRY JIT_WriteBarrier_PreGrow64, _TEXT + align 8 + ; Do the move into the GC . It is correct to take an AV here, the EH code + ; figures out that this came from a WriteBarrier and correctly maps it back + ; to the managed method which called the WriteBarrier (see setup in + ; InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rcx], rdx + + NOP_3_BYTE ; padding for alignment of constant + + ; Can't compare a 64 bit immediate, so we have to move it into a + ; register. Value of this immediate will be patched at runtime. +PATCH_LABEL JIT_WriteBarrier_PreGrow64_Patch_Label_Lower + mov rax, 0F0F0F0F0F0F0F0F0h + + ; Check the lower ephemeral region bound. + cmp rdx, rax + jb Exit + + nop ; padding for alignment of constant + +PATCH_LABEL JIT_WriteBarrier_PreGrow64_Patch_Label_CardTable + mov rax, 0F0F0F0F0F0F0F0F0h + + ; Touch the card table entry, if not already dirty. + shr rcx, 0Bh + cmp byte ptr [rcx + rax], 0FFh + jne UpdateCardTable + REPRET + + UpdateCardTable: + mov byte ptr [rcx + rax], 0FFh + ret + + align 16 + Exit: + REPRET +LEAF_END_MARKED JIT_WriteBarrier_PreGrow64, _TEXT + + +; See comments for JIT_WriteBarrier_PreGrow (above). +LEAF_ENTRY JIT_WriteBarrier_PostGrow64, _TEXT + align 8 + ; Do the move into the GC . It is correct to take an AV here, the EH code + ; figures out that this came from a WriteBarrier and correctly maps it back + ; to the managed method which called the WriteBarrier (see setup in + ; InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rcx], rdx + + NOP_3_BYTE ; padding for alignment of constant + + ; Can't compare a 64 bit immediate, so we have to move them into a + ; register. Values of these immediates will be patched at runtime. + ; By using two registers we can pipeline better. Should we decide to use + ; a special non-volatile calling convention, this should be changed to + ; just one. +PATCH_LABEL JIT_WriteBarrier_PostGrow64_Patch_Label_Lower + mov rax, 0F0F0F0F0F0F0F0F0h + + ; Check the lower and upper ephemeral region bounds + cmp rdx, rax + jb Exit + + nop ; padding for alignment of constant + +PATCH_LABEL JIT_WriteBarrier_PostGrow64_Patch_Label_Upper + mov r8, 0F0F0F0F0F0F0F0F0h + + cmp rdx, r8 + jae Exit + + nop ; padding for alignment of constant + +PATCH_LABEL JIT_WriteBarrier_PostGrow64_Patch_Label_CardTable + mov rax, 0F0F0F0F0F0F0F0F0h + + ; Touch the card table entry, if not already dirty. + shr rcx, 0Bh + cmp byte ptr [rcx + rax], 0FFh + jne UpdateCardTable + REPRET + + UpdateCardTable: + mov byte ptr [rcx + rax], 0FFh + ret + + align 16 + Exit: + REPRET +LEAF_END_MARKED JIT_WriteBarrier_PostGrow64, _TEXT + + +ifdef FEATURE_SVR_GC + +LEAF_ENTRY JIT_WriteBarrier_SVR64, _TEXT + align 8 + ; + ; SVR GC has multiple heaps, so it cannot provide one single + ; ephemeral region to bounds check against, so we just skip the + ; bounds checking all together and do our card table update + ; unconditionally. + ; + + ; Do the move into the GC . It is correct to take an AV here, the EH code + ; figures out that this came from a WriteBarrier and correctly maps it back + ; to the managed method which called the WriteBarrier (see setup in + ; InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rcx], rdx + + NOP_3_BYTE ; padding for alignment of constant + +PATCH_LABEL JIT_WriteBarrier_SVR64_PatchLabel_CardTable + mov rax, 0F0F0F0F0F0F0F0F0h + + shr rcx, 0Bh + + cmp byte ptr [rcx + rax], 0FFh + jne UpdateCardTable + REPRET + + UpdateCardTable: + mov byte ptr [rcx + rax], 0FFh + ret +LEAF_END_MARKED JIT_WriteBarrier_SVR64, _TEXT + +endif + + +ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + +LEAF_ENTRY JIT_WriteBarrier_WriteWatch_PreGrow64, _TEXT + align 8 + + ; Regarding patchable constants: + ; - 64-bit constants have to be loaded into a register + ; - The constants have to be aligned to 8 bytes so that they can be patched easily + ; - The constant loads have been located to minimize NOP padding required to align the constants + ; - Using different registers for successive constant loads helps pipeline better. Should we decide to use a special + ; non-volatile calling convention, this should be changed to use just one register. + + ; Do the move into the GC . It is correct to take an AV here, the EH code + ; figures out that this came from a WriteBarrier and correctly maps it back + ; to the managed method which called the WriteBarrier (see setup in + ; InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rcx], rdx + + ; Update the write watch table if necessary + mov rax, rcx +PATCH_LABEL JIT_WriteBarrier_WriteWatch_PreGrow64_Patch_Label_WriteWatchTable + mov r8, 0F0F0F0F0F0F0F0F0h + shr rax, 0Ch ; SoftwareWriteWatch::AddressToTableByteIndexShift + NOP_2_BYTE ; padding for alignment of constant +PATCH_LABEL JIT_WriteBarrier_WriteWatch_PreGrow64_Patch_Label_Lower + mov r9, 0F0F0F0F0F0F0F0F0h + add rax, r8 + cmp byte ptr [rax], 0h + jne CheckCardTable + mov byte ptr [rax], 0FFh + + ; Check the lower ephemeral region bound. + CheckCardTable: + cmp rdx, r9 + jb Exit + + ; Touch the card table entry, if not already dirty. + shr rcx, 0Bh + NOP_2_BYTE ; padding for alignment of constant +PATCH_LABEL JIT_WriteBarrier_WriteWatch_PreGrow64_Patch_Label_CardTable + mov rax, 0F0F0F0F0F0F0F0F0h + cmp byte ptr [rcx + rax], 0FFh + jne UpdateCardTable + REPRET + + UpdateCardTable: + mov byte ptr [rcx + rax], 0FFh + ret + + align 16 + Exit: + REPRET +LEAF_END_MARKED JIT_WriteBarrier_WriteWatch_PreGrow64, _TEXT + + +LEAF_ENTRY JIT_WriteBarrier_WriteWatch_PostGrow64, _TEXT + align 8 + + ; Regarding patchable constants: + ; - 64-bit constants have to be loaded into a register + ; - The constants have to be aligned to 8 bytes so that they can be patched easily + ; - The constant loads have been located to minimize NOP padding required to align the constants + ; - Using different registers for successive constant loads helps pipeline better. Should we decide to use a special + ; non-volatile calling convention, this should be changed to use just one register. + + ; Do the move into the GC . It is correct to take an AV here, the EH code + ; figures out that this came from a WriteBarrier and correctly maps it back + ; to the managed method which called the WriteBarrier (see setup in + ; InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rcx], rdx + + ; Update the write watch table if necessary + mov rax, rcx +PATCH_LABEL JIT_WriteBarrier_WriteWatch_PostGrow64_Patch_Label_WriteWatchTable + mov r8, 0F0F0F0F0F0F0F0F0h + shr rax, 0Ch ; SoftwareWriteWatch::AddressToTableByteIndexShift + NOP_2_BYTE ; padding for alignment of constant +PATCH_LABEL JIT_WriteBarrier_WriteWatch_PostGrow64_Patch_Label_Lower + mov r9, 0F0F0F0F0F0F0F0F0h + add rax, r8 + cmp byte ptr [rax], 0h + jne CheckCardTable + mov byte ptr [rax], 0FFh + + NOP_3_BYTE ; padding for alignment of constant + + ; Check the lower and upper ephemeral region bounds + CheckCardTable: + cmp rdx, r9 + jb Exit + + NOP_3_BYTE ; padding for alignment of constant + +PATCH_LABEL JIT_WriteBarrier_WriteWatch_PostGrow64_Patch_Label_Upper + mov r8, 0F0F0F0F0F0F0F0F0h + + cmp rdx, r8 + jae Exit + + nop ; padding for alignment of constant + +PATCH_LABEL JIT_WriteBarrier_WriteWatch_PostGrow64_Patch_Label_CardTable + mov rax, 0F0F0F0F0F0F0F0F0h + + ; Touch the card table entry, if not already dirty. + shr rcx, 0Bh + cmp byte ptr [rcx + rax], 0FFh + jne UpdateCardTable + REPRET + + UpdateCardTable: + mov byte ptr [rcx + rax], 0FFh + ret + + align 16 + Exit: + REPRET +LEAF_END_MARKED JIT_WriteBarrier_WriteWatch_PostGrow64, _TEXT + + +ifdef FEATURE_SVR_GC + +LEAF_ENTRY JIT_WriteBarrier_WriteWatch_SVR64, _TEXT + align 8 + + ; Regarding patchable constants: + ; - 64-bit constants have to be loaded into a register + ; - The constants have to be aligned to 8 bytes so that they can be patched easily + ; - The constant loads have been located to minimize NOP padding required to align the constants + ; - Using different registers for successive constant loads helps pipeline better. Should we decide to use a special + ; non-volatile calling convention, this should be changed to use just one register. + + ; + ; SVR GC has multiple heaps, so it cannot provide one single + ; ephemeral region to bounds check against, so we just skip the + ; bounds checking all together and do our card table update + ; unconditionally. + ; + + ; Do the move into the GC . It is correct to take an AV here, the EH code + ; figures out that this came from a WriteBarrier and correctly maps it back + ; to the managed method which called the WriteBarrier (see setup in + ; InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rcx], rdx + + ; Update the write watch table if necessary + mov rax, rcx +PATCH_LABEL JIT_WriteBarrier_WriteWatch_SVR64_PatchLabel_WriteWatchTable + mov r8, 0F0F0F0F0F0F0F0F0h + shr rax, 0Ch ; SoftwareWriteWatch::AddressToTableByteIndexShift + NOP_2_BYTE ; padding for alignment of constant +PATCH_LABEL JIT_WriteBarrier_WriteWatch_SVR64_PatchLabel_CardTable + mov r9, 0F0F0F0F0F0F0F0F0h + add rax, r8 + cmp byte ptr [rax], 0h + jne CheckCardTable + mov byte ptr [rax], 0FFh + + CheckCardTable: + shr rcx, 0Bh + cmp byte ptr [rcx + r9], 0FFh + jne UpdateCardTable + REPRET + + UpdateCardTable: + mov byte ptr [rcx + r9], 0FFh + ret +LEAF_END_MARKED JIT_WriteBarrier_WriteWatch_SVR64, _TEXT + +endif +endif + + + end diff --git a/src/vm/amd64/JitHelpers_InlineGetAppDomain.asm b/src/vm/amd64/JitHelpers_InlineGetAppDomain.asm new file mode 100644 index 0000000000..187decf14d --- /dev/null +++ b/src/vm/amd64/JitHelpers_InlineGetAppDomain.asm @@ -0,0 +1,123 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; ==++== +; + +; +; ==--== +; *********************************************************************** +; File: JitHelpers_InlineGetAppDomain.asm, see history in jithelp.asm +; +; Notes: These routinues will be patched at runtime with the location in +; the TLS to find the AppDomain* and are the fastest implementation +; of their specific functionality. +; *********************************************************************** + +include AsmMacros.inc +include asmconstants.inc + +; Min amount of stack space that a nested function should allocate. +MIN_SIZE equ 28h + +; Macro to create a patchable inline GetAppdomain, if we decide to create patchable +; high TLS inline versions then just change this macro to make sure to create enough +; space in the asm to patch the high TLS getter instructions. +PATCHABLE_INLINE_GETAPPDOMAIN macro Reg, PatchLabel +PATCH_LABEL PatchLabel + mov Reg, gs:[OFFSET__TEB__TlsSlots] + endm + +extern JIT_GetSharedNonGCStaticBase_Helper:proc +extern JIT_GetSharedGCStaticBase_Helper:proc + +LEAF_ENTRY JIT_GetSharedNonGCStaticBase_InlineGetAppDomain, _TEXT + ; Check if rcx (moduleDomainID) is not a moduleID + mov rax, rcx + test rax, 1 + jz HaveLocalModule + + PATCHABLE_INLINE_GETAPPDOMAIN rax, JIT_GetSharedNonGCStaticBase__PatchTLSLabel + + ; Get the LocalModule, rcx will always be odd, so: rcx * 4 - 4 <=> (rcx >> 1) * 8 + mov rax, [rax + OFFSETOF__AppDomain__m_sDomainLocalBlock + OFFSETOF__DomainLocalBlock__m_pModuleSlots] + mov rax, [rax + rcx * 4 - 4] + + HaveLocalModule: + ; If class is not initialized, bail to C++ helper + test byte ptr [rax + OFFSETOF__DomainLocalModule__m_pDataBlob + rdx], 1 + jz CallHelper + REPRET + + align 16 + CallHelper: + ; Tail call JIT_GetSharedNonGCStaticBase_Helper + mov rcx, rax + jmp JIT_GetSharedNonGCStaticBase_Helper +LEAF_END JIT_GetSharedNonGCStaticBase_InlineGetAppDomain, _TEXT + +LEAF_ENTRY JIT_GetSharedNonGCStaticBaseNoCtor_InlineGetAppDomain, _TEXT + ; Check if rcx (moduleDomainID) is not a moduleID + mov rax, rcx + test rax, 1 + jz HaveLocalModule + + PATCHABLE_INLINE_GETAPPDOMAIN rax, JIT_GetSharedNonGCStaticBaseNoCtor__PatchTLSLabel + + ; Get the LocalModule, rcx will always be odd, so: rcx * 4 - 4 <=> (rcx >> 1) * 8 + mov rax, [rax + OFFSETOF__AppDomain__m_sDomainLocalBlock + OFFSETOF__DomainLocalBlock__m_pModuleSlots] + mov rax, [rax + rcx * 4 - 4] + ret + + align 16 + HaveLocalModule: + REPRET +LEAF_END JIT_GetSharedNonGCStaticBaseNoCtor_InlineGetAppDomain, _TEXT + +LEAF_ENTRY JIT_GetSharedGCStaticBase_InlineGetAppDomain, _TEXT + ; Check if rcx (moduleDomainID) is not a moduleID + mov rax, rcx + test rax, 1 + jz HaveLocalModule + + PATCHABLE_INLINE_GETAPPDOMAIN rax, JIT_GetSharedGCStaticBase__PatchTLSLabel + + ; Get the LocalModule, rcx will always be odd, so: rcx * 4 - 4 <=> (rcx >> 1) * 8 + mov rax, [rax + OFFSETOF__AppDomain__m_sDomainLocalBlock + OFFSETOF__DomainLocalBlock__m_pModuleSlots] + mov rax, [rax + rcx * 4 - 4] + + HaveLocalModule: + ; If class is not initialized, bail to C++ helper + test byte ptr [rax + OFFSETOF__DomainLocalModule__m_pDataBlob + rdx], 1 + jz CallHelper + + mov rax, [rax + OFFSETOF__DomainLocalModule__m_pGCStatics] + ret + + align 16 + CallHelper: + ; Tail call Jit_GetSharedGCStaticBase_Helper + mov rcx, rax + jmp JIT_GetSharedGCStaticBase_Helper +LEAF_END JIT_GetSharedGCStaticBase_InlineGetAppDomain, _TEXT + +LEAF_ENTRY JIT_GetSharedGCStaticBaseNoCtor_InlineGetAppDomain, _TEXT + ; Check if rcx (moduleDomainID) is not a moduleID + mov rax, rcx + test rax, 1 + jz HaveLocalModule + + PATCHABLE_INLINE_GETAPPDOMAIN rax, JIT_GetSharedGCStaticBaseNoCtor__PatchTLSLabel + + ; Get the LocalModule, rcx will always be odd, so: rcx * 4 - 4 <=> (rcx >> 1) * 8 + mov rax, [rax + OFFSETOF__AppDomain__m_sDomainLocalBlock + OFFSETOF__DomainLocalBlock__m_pModuleSlots] + mov rax, [rax + rcx * 4 - 4] + + HaveLocalModule: + mov rax, [rax + OFFSETOF__DomainLocalModule__m_pGCStatics] + ret +LEAF_END JIT_GetSharedGCStaticBaseNoCtor_InlineGetAppDomain, _TEXT + + end + diff --git a/src/vm/amd64/JitHelpers_InlineGetThread.asm b/src/vm/amd64/JitHelpers_InlineGetThread.asm new file mode 100644 index 0000000000..700c3b393c --- /dev/null +++ b/src/vm/amd64/JitHelpers_InlineGetThread.asm @@ -0,0 +1,1335 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; ==++== +; + +; +; ==--== +; *********************************************************************** +; File: JitHelpers_InlineGetThread.asm, see history in jithelp.asm +; +; Notes: These routinues will be patched at runtime with the location in +; the TLS to find the Thread* and are the fastest implementation +; of their specific functionality. +; *********************************************************************** + +include AsmMacros.inc +include asmconstants.inc + +; Min amount of stack space that a nested function should allocate. +MIN_SIZE equ 28h + +; Macro to create a patchable inline GetAppdomain, if we decide to create patchable +; high TLS inline versions then just change this macro to make sure to create enough +; space in the asm to patch the high TLS getter instructions. +PATCHABLE_INLINE_GETTHREAD macro Reg, PatchLabel +PATCH_LABEL PatchLabel + mov Reg, gs:[OFFSET__TEB__TlsSlots] + endm + + +JIT_NEW equ ?JIT_New@@YAPEAVObject@@PEAUCORINFO_CLASS_STRUCT_@@@Z +Object__DEBUG_SetAppDomain equ ?DEBUG_SetAppDomain@Object@@QEAAXPEAVAppDomain@@@Z +CopyValueClassUnchecked equ ?CopyValueClassUnchecked@@YAXPEAX0PEAVMethodTable@@@Z +JIT_Box equ ?JIT_Box@@YAPEAVObject@@PEAUCORINFO_CLASS_STRUCT_@@PEAX@Z +g_pStringClass equ ?g_pStringClass@@3PEAVMethodTable@@EA +FramedAllocateString equ ?FramedAllocateString@@YAPEAVStringObject@@K@Z +JIT_NewArr1 equ ?JIT_NewArr1@@YAPEAVObject@@PEAUCORINFO_CLASS_STRUCT_@@_J@Z + +INVALIDGCVALUE equ 0CCCCCCCDh + +extern JIT_NEW:proc +extern CopyValueClassUnchecked:proc +extern JIT_Box:proc +extern g_pStringClass:QWORD +extern FramedAllocateString:proc +extern JIT_NewArr1:proc + +extern JIT_InternalThrow:proc + +ifdef _DEBUG +extern DEBUG_TrialAllocSetAppDomain:proc +extern DEBUG_TrialAllocSetAppDomain_NoScratchArea:proc +endif + +; IN: rcx: MethodTable* +; OUT: rax: new object +LEAF_ENTRY JIT_TrialAllocSFastMP_InlineGetThread, _TEXT + mov edx, [rcx + OFFSET__MethodTable__m_BaseSize] + + ; m_BaseSize is guaranteed to be a multiple of 8. + + PATCHABLE_INLINE_GETTHREAD r11, JIT_TrialAllocSFastMP_InlineGetThread__PatchTLSOffset + mov r10, [r11 + OFFSET__Thread__m_alloc_context__alloc_limit] + mov rax, [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr] + + add rdx, rax + + cmp rdx, r10 + ja AllocFailed + + mov [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr], rdx + mov [rax], rcx + +ifdef _DEBUG + call DEBUG_TrialAllocSetAppDomain_NoScratchArea +endif ; _DEBUG + + ret + + AllocFailed: + jmp JIT_NEW +LEAF_END JIT_TrialAllocSFastMP_InlineGetThread, _TEXT + +; HCIMPL2(Object*, JIT_Box, CORINFO_CLASS_HANDLE type, void* unboxedData) +NESTED_ENTRY JIT_BoxFastMP_InlineGetThread, _TEXT + mov rax, [rcx + OFFSETOF__MethodTable__m_pWriteableData] + + ; Check whether the class has not been initialized + test dword ptr [rax + OFFSETOF__MethodTableWriteableData__m_dwFlags], MethodTableWriteableData__enum_flag_Unrestored + jnz ClassNotInited + + mov r8d, [rcx + OFFSET__MethodTable__m_BaseSize] + + ; m_BaseSize is guaranteed to be a multiple of 8. + + PATCHABLE_INLINE_GETTHREAD r11, JIT_BoxFastMPIGT__PatchTLSLabel + mov r10, [r11 + OFFSET__Thread__m_alloc_context__alloc_limit] + mov rax, [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr] + + add r8, rax + + cmp r8, r10 + ja AllocFailed + + mov [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr], r8 + mov [rax], rcx + +ifdef _DEBUG + call DEBUG_TrialAllocSetAppDomain_NoScratchArea +endif ; _DEBUG + + ; Check whether the object contains pointers + test dword ptr [rcx + OFFSETOF__MethodTable__m_dwFlags], MethodTable__enum_flag_ContainsPointers + jnz ContainsPointers + + ; We have no pointers - emit a simple inline copy loop + ; Copy the contents from the end + mov ecx, [rcx + OFFSET__MethodTable__m_BaseSize] + sub ecx, 18h ; sizeof(ObjHeader) + sizeof(Object) + last slot + +align 16 + CopyLoop: + mov r8, [rdx+rcx] + mov [rax+rcx+8], r8 + sub ecx, 8 + jge CopyLoop + REPRET + + ContainsPointers: + ; Do call to CopyValueClassUnchecked(object, data, pMT) + push_vol_reg rax + alloc_stack 20h + END_PROLOGUE + + mov r8, rcx + lea rcx, [rax + 8] + call CopyValueClassUnchecked + + add rsp, 20h + pop rax + ret + + ClassNotInited: + AllocFailed: + jmp JIT_Box +NESTED_END JIT_BoxFastMP_InlineGetThread, _TEXT + +FIX_INDIRECTION macro Reg +ifdef FEATURE_PREJIT + test Reg, 1 + jz @F + mov Reg, [Reg-1] + @@: +endif +endm + +LEAF_ENTRY AllocateStringFastMP_InlineGetThread, _TEXT + ; We were passed the number of characters in ECX + + ; we need to load the method table for string from the global + mov r9, [g_pStringClass] + + ; Instead of doing elaborate overflow checks, we just limit the number of elements + ; to (LARGE_OBJECT_SIZE - 256)/sizeof(WCHAR) or less. + ; This will avoid all overflow problems, as well as making sure + ; big string objects are correctly allocated in the big object heap. + + cmp ecx, (ASM_LARGE_OBJECT_SIZE - 256)/2 + jae OversizedString + + mov edx, [r9 + OFFSET__MethodTable__m_BaseSize] + + ; Calculate the final size to allocate. + ; We need to calculate baseSize + cnt*2, then round that up by adding 7 and anding ~7. + + lea edx, [edx + ecx*2 + 7] + and edx, -8 + + PATCHABLE_INLINE_GETTHREAD r11, AllocateStringFastMP_InlineGetThread__PatchTLSOffset + mov r10, [r11 + OFFSET__Thread__m_alloc_context__alloc_limit] + mov rax, [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr] + + add rdx, rax + + cmp rdx, r10 + ja AllocFailed + + mov [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr], rdx + mov [rax], r9 + + mov [rax + OFFSETOF__StringObject__m_StringLength], ecx + +ifdef _DEBUG + call DEBUG_TrialAllocSetAppDomain_NoScratchArea +endif ; _DEBUG + + ret + + OversizedString: + AllocFailed: + jmp FramedAllocateString +LEAF_END AllocateStringFastMP_InlineGetThread, _TEXT + +; HCIMPL2(Object*, JIT_NewArr1, CORINFO_CLASS_HANDLE arrayTypeHnd_, INT_PTR size) +LEAF_ENTRY JIT_NewArr1VC_MP_InlineGetThread, _TEXT + ; We were passed a type descriptor in RCX, which contains the (shared) + ; array method table and the element type. + + ; The element count is in RDX + + ; NOTE: if this code is ported for CORINFO_HELP_NEWSFAST_ALIGN8, it will need + ; to emulate the double-specific behavior of JIT_TrialAlloc::GenAllocArray. + + ; Do a conservative check here. This is to avoid overflow while doing the calculations. We don't + ; have to worry about "large" objects, since the allocation quantum is never big enough for + ; LARGE_OBJECT_SIZE. + + ; For Value Classes, this needs to be 2^16 - slack (2^32 / max component size), + ; The slack includes the size for the array header and round-up ; for alignment. Use 256 for the + ; slack value out of laziness. + + ; In both cases we do a final overflow check after adding to the alloc_ptr. + + ; we need to load the true method table from the type desc + mov r9, [rcx + OFFSETOF__ArrayTypeDesc__m_TemplateMT - 2] + + FIX_INDIRECTION r9 + + cmp rdx, (65535 - 256) + jae OversizedArray + + movzx r8d, word ptr [r9 + OFFSETOF__MethodTable__m_dwFlags] ; component size is low 16 bits + imul r8d, edx + add r8d, dword ptr [r9 + OFFSET__MethodTable__m_BaseSize] + + ; round the size to a multiple of 8 + + add r8d, 7 + and r8d, -8 + + + PATCHABLE_INLINE_GETTHREAD r11, JIT_NewArr1VC_MP_InlineGetThread__PatchTLSOffset + mov r10, [r11 + OFFSET__Thread__m_alloc_context__alloc_limit] + mov rax, [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr] + + add r8, rax + jc AllocFailed + + cmp r8, r10 + ja AllocFailed + + mov [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr], r8 + mov [rax], r9 + + mov dword ptr [rax + OFFSETOF__ArrayBase__m_NumComponents], edx + +ifdef _DEBUG + call DEBUG_TrialAllocSetAppDomain_NoScratchArea +endif ; _DEBUG + + ret + + OversizedArray: + AllocFailed: + jmp JIT_NewArr1 +LEAF_END JIT_NewArr1VC_MP_InlineGetThread, _TEXT + + +; HCIMPL2(Object*, JIT_NewArr1, CORINFO_CLASS_HANDLE arrayTypeHnd_, INT_PTR size) +LEAF_ENTRY JIT_NewArr1OBJ_MP_InlineGetThread, _TEXT + ; We were passed a type descriptor in RCX, which contains the (shared) + ; array method table and the element type. + + ; The element count is in RDX + + ; NOTE: if this code is ported for CORINFO_HELP_NEWSFAST_ALIGN8, it will need + ; to emulate the double-specific behavior of JIT_TrialAlloc::GenAllocArray. + + ; Verifies that LARGE_OBJECT_SIZE fits in 32-bit. This allows us to do array size + ; arithmetic using 32-bit registers. + .erre ASM_LARGE_OBJECT_SIZE lt 100000000h + + cmp rdx, (ASM_LARGE_OBJECT_SIZE - 256)/8 ; sizeof(void*) + jae OversizedArray + + ; we need to load the true method table from the type desc + mov r9, [rcx + OFFSETOF__ArrayTypeDesc__m_TemplateMT - 2] + + FIX_INDIRECTION r9 + + ; In this case we know the element size is sizeof(void *), or 8 for x64 + ; This helps us in two ways - we can shift instead of multiplying, and + ; there's no need to align the size either + + mov r8d, dword ptr [r9 + OFFSET__MethodTable__m_BaseSize] + lea r8d, [r8d + edx * 8] + + ; No need for rounding in this case - element size is 8, and m_BaseSize is guaranteed + ; to be a multiple of 8. + + PATCHABLE_INLINE_GETTHREAD r11, JIT_NewArr1OBJ_MP_InlineGetThread__PatchTLSOffset + mov r10, [r11 + OFFSET__Thread__m_alloc_context__alloc_limit] + mov rax, [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr] + + add r8, rax + + cmp r8, r10 + ja AllocFailed + + mov [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr], r8 + mov [rax], r9 + + mov dword ptr [rax + OFFSETOF__ArrayBase__m_NumComponents], edx + +ifdef _DEBUG + call DEBUG_TrialAllocSetAppDomain_NoScratchArea +endif ; _DEBUG + + ret + + OversizedArray: + AllocFailed: + jmp JIT_NewArr1 +LEAF_END JIT_NewArr1OBJ_MP_InlineGetThread, _TEXT + + +MON_ENTER_STACK_SIZE equ 00000020h +MON_EXIT_STACK_SIZE equ 00000068h + +ifdef MON_DEBUG +ifdef TRACK_SYNC +MON_ENTER_STACK_SIZE_INLINEGETTHREAD equ 00000020h +MON_EXIT_STACK_SIZE_INLINEGETTHREAD equ 00000068h +endif +endif + +BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX equ 08000000h ; syncblk.h +BIT_SBLK_IS_HASHCODE equ 04000000h ; syncblk.h +BIT_SBLK_SPIN_LOCK equ 10000000h ; syncblk.h + +SBLK_MASK_LOCK_THREADID equ 000003FFh ; syncblk.h +SBLK_LOCK_RECLEVEL_INC equ 00000400h ; syncblk.h +SBLK_MASK_LOCK_RECLEVEL equ 0000FC00h ; syncblk.h + +MASK_SYNCBLOCKINDEX equ 03FFFFFFh ; syncblk.h +STATE_CHECK equ 0FFFFFFFEh + +MT_CTX_PROXY_FLAG equ 10000000h + +g_pSyncTable equ ?g_pSyncTable@@3PEAVSyncTableEntry@@EA +g_SystemInfo equ ?g_SystemInfo@@3U_SYSTEM_INFO@@A +g_SpinConstants equ ?g_SpinConstants@@3USpinConstants@@A + +extern g_pSyncTable:QWORD +extern g_SystemInfo:QWORD +extern g_SpinConstants:QWORD + +; JITutil_MonEnterWorker(Object* obj, BYTE* pbLockTaken) +extern JITutil_MonEnterWorker:proc +; JITutil_MonTryEnter(Object* obj, INT32 timeout, BYTE* pbLockTaken) +extern JITutil_MonTryEnter:proc +; JITutil_MonExitWorker(Object* obj, BYTE* pbLockTaken) +extern JITutil_MonExitWorker:proc +; JITutil_MonSignal(AwareLock* lock, BYTE* pbLockTaken) +extern JITutil_MonSignal:proc +; JITutil_MonContention(AwareLock* lock, BYTE* pbLockTaken) +extern JITutil_MonContention:proc + +ifdef _DEBUG +MON_DEBUG equ 1 +endif + +ifdef MON_DEBUG +ifdef TRACK_SYNC +extern EnterSyncHelper:proc +extern LeaveSyncHelper:proc +endif +endif + + +MON_ENTER_EPILOG_ADJUST_STACK macro +ifdef MON_DEBUG +ifdef TRACK_SYNC + add rsp, MON_ENTER_STACK_SIZE_INLINEGETTHREAD +endif +endif + endm + + +MON_ENTER_RETURN_SUCCESS macro + ; This is sensitive to the potential that pbLockTaken is NULL + test rsi, rsi + jz @F + mov byte ptr [rsi], 1 + @@: + MON_ENTER_EPILOG_ADJUST_STACK + pop rsi + ret + + endm + + +; The worker versions of these functions are smart about the potential for pbLockTaken +; to be NULL, and if it is then they treat it as if they don't have a state variable. +; This is because when locking is not inserted by the JIT (instead by explicit calls to +; Monitor.Enter() and Monitor.Exit()) we will call these guys. +; +; This is a frameless helper for entering a monitor on a object. +; The object is in ARGUMENT_REG1. This tries the normal case (no +; blocking or object allocation) in line and calls a framed helper +; for the other cases. +; +; EXTERN_C void JIT_MonEnterWorker_InlineGetThread(Object* obj, /*OUT*/ BYTE* pbLockTaken) +JIT_HELPER_MONITOR_THUNK JIT_MonEnter, _TEXT +NESTED_ENTRY JIT_MonEnterWorker_InlineGetThread, _TEXT + push_nonvol_reg rsi +ifdef MON_DEBUG +ifdef TRACK_SYNC + alloc_stack MON_ENTER_STACK_SIZE_INLINEGETTHREAD + + save_reg_postrsp rcx, MON_ENTER_STACK_SIZE_INLINEGETTHREAD + 10h + 0h + save_reg_postrsp rdx, MON_ENTER_STACK_SIZE_INLINEGETTHREAD + 10h + 8h + save_reg_postrsp r8, MON_ENTER_STACK_SIZE_INLINEGETTHREAD + 10h + 10h + save_reg_postrsp r9, MON_ENTER_STACK_SIZE_INLINEGETTHREAD + 10h + 18h +endif +endif + END_PROLOGUE + + ; Put pbLockTaken in rsi, this can be null + mov rsi, rdx + + ; Check if the instance is NULL + test rcx, rcx + jz FramedLockHelper + + PATCHABLE_INLINE_GETTHREAD r11, JIT_MonEnterWorker_InlineGetThread_GetThread_PatchLabel + + ; Initialize delay value for retry with exponential backoff + mov r10d, dword ptr [g_SpinConstants + OFFSETOF__g_SpinConstants__dwInitialDuration] + + ; Check if we can abort here + mov eax, dword ptr [r11 + OFFSETOF__Thread__m_State] + and eax, THREAD_CATCHATSAFEPOINT_BITS + ; Go through the slow code path to initiate ThreadAbort + jnz FramedLockHelper + + ; r8 will hold the syncblockindex address + lea r8, [rcx - OFFSETOF__ObjHeader__SyncBlkIndex] + + RetryThinLock: + ; Fetch the syncblock dword + mov eax, dword ptr [r8] + + ; Check whether we have the "thin lock" layout, the lock is free and the spin lock bit is not set + test eax, BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX + BIT_SBLK_SPIN_LOCK + SBLK_MASK_LOCK_THREADID + SBLK_MASK_LOCK_RECLEVEL + jnz NeedMoreTests + + ; Everything is fine - get the thread id to store in the lock + mov edx, dword ptr [r11 + OFFSETOF__Thread__m_ThreadId] + + ; If the thread id is too large, we need a syncblock for sure + cmp edx, SBLK_MASK_LOCK_THREADID + ja FramedLockHelper + + ; We want to store a new value with the current thread id set in the low 10 bits + or edx, eax + lock cmpxchg dword ptr [r8], edx + jnz PrepareToWaitThinLock + + ; Everything went fine and we're done + add dword ptr [r11 + OFFSETOF__Thread__m_dwLockCount], 1 + + ; Done, leave and set pbLockTaken if we have it + MON_ENTER_RETURN_SUCCESS + + NeedMoreTests: + ; OK, not the simple case, find out which case it is + test eax, BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX + jnz HaveHashOrSyncBlockIndex + + ; The header is transitioning or the lock, treat this as if the lock was taken + test eax, BIT_SBLK_SPIN_LOCK + jnz PrepareToWaitThinLock + + ; Here we know we have the "thin lock" layout, but the lock is not free. + ; It could still be the recursion case, compare the thread id to check + mov edx, eax + and edx, SBLK_MASK_LOCK_THREADID + cmp edx, dword ptr [r11 + OFFSETOF__Thread__m_ThreadId] + jne PrepareToWaitThinLock + + ; Ok, the thread id matches, it's the recursion case. + ; Bump up the recursion level and check for overflow + lea edx, [eax + SBLK_LOCK_RECLEVEL_INC] + test edx, SBLK_MASK_LOCK_RECLEVEL + jz FramedLockHelper + + ; Try to put the new recursion level back. If the header was changed in the meantime + ; we need a full retry, because the layout could have changed + lock cmpxchg dword ptr [r8], edx + jnz RetryHelperThinLock + + ; Done, leave and set pbLockTaken if we have it + MON_ENTER_RETURN_SUCCESS + + PrepareToWaitThinLock: + ; If we are on an MP system, we try spinning for a certain number of iterations + cmp dword ptr [g_SystemInfo + OFFSETOF__g_SystemInfo__dwNumberOfProcessors], 1 + jle FramedLockHelper + + ; Exponential backoff; delay by approximately 2*r10 clock cycles + mov eax, r10d + delayLoopThinLock: + pause ; indicate to the CPU that we are spin waiting + sub eax, 1 + jnz delayLoopThinLock + + ; Next time, wait a factor longer + imul r10d, dword ptr [g_SpinConstants + OFFSETOF__g_SpinConstants__dwBackoffFactor] + + cmp r10d, dword ptr [g_SpinConstants + OFFSETOF__g_SpinConstants__dwMaximumDuration] + jle RetryHelperThinLock + + jmp FramedLockHelper + + RetryHelperThinLock: + jmp RetryThinLock + + HaveHashOrSyncBlockIndex: + ; If we have a hash code already, we need to create a sync block + test eax, BIT_SBLK_IS_HASHCODE + jnz FramedLockHelper + + ; OK, we have a sync block index, just and out the top bits and grab the synblock index + and eax, MASK_SYNCBLOCKINDEX + + ; Get the sync block pointer + mov rdx, qword ptr [g_pSyncTable] + shl eax, 4h + mov rdx, [rdx + rax + OFFSETOF__SyncTableEntry__m_SyncBlock] + + ; Check if the sync block has been allocated + test rdx, rdx + jz FramedLockHelper + + ; Get a pointer to the lock object + lea rdx, [rdx + OFFSETOF__SyncBlock__m_Monitor] + + ; Attempt to acquire the lock + RetrySyncBlock: + mov eax, dword ptr [rdx + OFFSETOF__AwareLock__m_MonitorHeld] + test eax, eax + jne HaveWaiters + + ; Common case, lock isn't held and there are no waiters. Attempt to + ; gain ownership ourselves + xor ecx, ecx + inc ecx + + lock cmpxchg dword ptr [rdx + OFFSETOF__AwareLock__m_MonitorHeld], ecx + jnz RetryHelperSyncBlock + + ; Success. Save the thread object in the lock and increment the use count + mov qword ptr [rdx + OFFSETOF__AwareLock__m_HoldingThread], r11 + add dword ptr [rdx + OFFSETOF__AwareLock__m_Recursion], 1 + add dword ptr [r11 + OFFSETOF__Thread__m_dwLockCount], 1 + +ifdef MON_DEBUG +ifdef TRACK_SYNC + mov rcx, [rsp + MON_ENTER_STACK_SIZE_INLINEGETTHREAD + 8h] ; return address + ; void EnterSyncHelper(UINT_PTR caller, AwareLock* lock) + call EnterSyncHelper +endif +endif + + ; Done, leave and set pbLockTaken if we have it + MON_ENTER_RETURN_SUCCESS + + ; It's possible to get here with waiters by no lock held, but in this + ; case a signal is about to be fired which will wake up the waiter. So + ; for fairness sake we should wait too. + ; Check first for recur11ve lock attempts on the same thread. + HaveWaiters: + ; Is mutex already owned by current thread? + cmp [rdx + OFFSETOF__AwareLock__m_HoldingThread], r11 + jne PrepareToWait + + ; Yes, bump our use count. + add dword ptr [rdx + OFFSETOF__AwareLock__m_Recursion], 1 + +ifdef MON_DEBUG +ifdef TRACK_SYNC + mov rcx, [rsp + MON_ENTER_STACK_SIZE_INLINEGETTHREAD + 8h] ; return address + ; void EnterSyncHelper(UINT_PTR caller, AwareLock* lock) + call EnterSyncHelper +endif +endif + ; Done, leave and set pbLockTaken if we have it + MON_ENTER_RETURN_SUCCESS + + PrepareToWait: + ; If we are on a MP system we try spinning for a certain number of iterations + cmp dword ptr [g_SystemInfo + OFFSETOF__g_SystemInfo__dwNumberOfProcessors], 1 + jle HaveWaiters1 + + ; Exponential backoff: delay by approximately 2*r10 clock cycles + mov eax, r10d + delayLoop: + pause ; indicate to the CPU that we are spin waiting + sub eax, 1 + jnz delayLoop + + ; Next time, wait a factor longer + imul r10d, dword ptr [g_SpinConstants + OFFSETOF__g_SpinConstants__dwBackoffFactor] + + cmp r10d, dword ptr [g_SpinConstants + OFFSETOF__g_SpinConstants__dwMaximumDuration] + jle RetrySyncBlock + + HaveWaiters1: + mov rcx, rdx + mov rdx, rsi + MON_ENTER_EPILOG_ADJUST_STACK + pop rsi + ; void JITutil_MonContention(AwareLock* lock, BYTE* pbLockTaken) + jmp JITutil_MonContention + + RetryHelperSyncBlock: + jmp RetrySyncBlock + + FramedLockHelper: + mov rdx, rsi + MON_ENTER_EPILOG_ADJUST_STACK + pop rsi + ; void JITutil_MonEnterWorker(Object* obj, BYTE* pbLockTaken) + jmp JITutil_MonEnterWorker + +NESTED_END JIT_MonEnterWorker_InlineGetThread, _TEXT + + +MON_EXIT_EPILOG_ADJUST_STACK macro +ifdef MON_DEBUG +ifdef TRACK_SYNC + add rsp, MON_EXIT_STACK_SIZE_INLINEGETTHREAD +endif +endif + endm + +MON_EXIT_RETURN_SUCCESS macro + ; This is sensitive to the potential that pbLockTaken is null + test r10, r10 + jz @F + mov byte ptr [r10], 0 + @@: + MON_EXIT_EPILOG_ADJUST_STACK + ret + + endm + + +; The worker versions of these functions are smart about the potential for pbLockTaken +; to be NULL, and if it is then they treat it as if they don't have a state variable. +; This is because when locking is not inserted by the JIT (instead by explicit calls to +; Monitor.Enter() and Monitor.Exit()) we will call these guys. +; +; This is a frameless helper for exiting a monitor on a object. +; The object is in ARGUMENT_REG1. This tries the normal case (no +; blocking or object allocation) in line and calls a framed helper +; for the other cases. +; +; void JIT_MonExitWorker_InlineGetThread(Object* obj, BYTE* pbLockTaken) +JIT_HELPER_MONITOR_THUNK JIT_MonExit, _TEXT +NESTED_ENTRY JIT_MonExitWorker_InlineGetThread, _TEXT + .savereg rcx, 0 +ifdef MON_DEBUG +ifdef TRACK_SYNC + alloc_stack MON_EXIT_STACK_SIZE_INLINEGETTHREAD + + save_reg_postrsp rcx, MON_EXIT_STACK_SIZE_INLINEGETTHREAD + 8h + 0h + save_reg_postrsp rdx, MON_EXIT_STACK_SIZE_INLINEGETTHREAD + 8h + 8h + save_reg_postrsp r8, MON_EXIT_STACK_SIZE_INLINEGETTHREAD + 8h + 10h + save_reg_postrsp r9, MON_EXIT_STACK_SIZE_INLINEGETTHREAD + 8h + 18h +endif +endif + END_PROLOGUE + + ; pbLockTaken is stored in r10, this can be null + mov r10, rdx + + ; if pbLockTaken is NULL then we got here without a state variable, avoid the + ; next comparison in that case as it will AV + test rdx, rdx + jz Null_pbLockTaken + + ; If the lock wasn't taken then we bail quickly without doing anything + cmp byte ptr [rdx], 0 + je LockNotTaken + + Null_pbLockTaken: + ; Check is the instance is null + test rcx, rcx + jz FramedLockHelper + + PATCHABLE_INLINE_GETTHREAD r11, JIT_MonExitWorker_InlineGetThread_GetThread_PatchLabel + + ; r8 will hold the syncblockindex address + lea r8, [rcx - OFFSETOF__ObjHeader__SyncBlkIndex] + + RetryThinLock: + ; Fetch the syncblock dword + mov eax, dword ptr [r8] + test eax, BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX + BIT_SBLK_SPIN_LOCK + jnz NeedMoreTests + + ; Ok, we have a "thin lock" layout - check whether the thread id matches + mov edx, eax + and edx, SBLK_MASK_LOCK_THREADID + cmp edx, dword ptr [r11 + OFFSETOF__Thread__m_ThreadId] + jne FramedLockHelper + + ; check the recursion level + test eax, SBLK_MASK_LOCK_RECLEVEL + jne DecRecursionLevel + + ; It's zero -- we're leaving the lock. + ; So try to put back a zero thread id. + ; edx and eax match in the thread id bits, and edx is zero else where, so the xor is sufficient + xor edx, eax + lock cmpxchg dword ptr [r8], edx + jnz RetryThinLockHelper1 ; forward jump to avoid mispredict on success + + ; Dec the dwLockCount on the thread + sub dword ptr [r11 + OFFSETOF__Thread__m_dwLockCount], 1 + + ; Done, leave and set pbLockTaken if we have it + MON_EXIT_RETURN_SUCCESS + + RetryThinLockHelper1: + jmp RetryThinLock + + DecRecursionLevel: + lea edx, [eax - SBLK_LOCK_RECLEVEL_INC] + lock cmpxchg dword ptr [r8], edx + jnz RetryThinLockHelper2 ; forward jump to avoid mispredict on success + + ; We're done, leave and set pbLockTaken if we have it + MON_EXIT_RETURN_SUCCESS + + RetryThinLockHelper2: + jmp RetryThinLock + + NeedMoreTests: + ; Forward all special cases to the slow helper + test eax, BIT_SBLK_IS_HASHCODE + BIT_SBLK_SPIN_LOCK + jnz FramedLockHelper + + ; Get the sync block index and use it to compute the sync block pointer + mov rdx, qword ptr [g_pSyncTable] + and eax, MASK_SYNCBLOCKINDEX + shl eax, 4 + mov rdx, [rdx + rax + OFFSETOF__SyncTableEntry__m_SyncBlock] + + ; Was there a sync block? + test rdx, rdx + jz FramedLockHelper + + ; Get a pointer to the lock object. + lea rdx, [rdx + OFFSETOF__SyncBlock__m_Monitor] + + ; Check if the lock is held. + cmp qword ptr [rdx + OFFSETOF__AwareLock__m_HoldingThread], r11 + jne FramedLockHelper + +ifdef MON_DEBUG +ifdef TRACK_SYNC + mov [rsp + 28h], rcx + mov [rsp + 30h], rdx + mov [rsp + 38h], r10 + mov [rsp + 40h], r11 + + mov rcx, [rsp + MON_EXIT_STACK_SIZE_INLINEGETTHREAD ] ; return address + ; void LeaveSyncHelper(UINT_PTR caller, AwareLock* lock) + call LeaveSyncHelper + + mov rcx, [rsp + 28h] + mov rdx, [rsp + 30h] + mov r10, [rsp + 38h] + mov r11, [rsp + 40h] +endif +endif + + ; Reduce our recursion count + sub dword ptr [rdx + OFFSETOF__AwareLock__m_Recursion], 1 + jz LastRecursion + + ; Done, leave and set pbLockTaken if we have it + MON_EXIT_RETURN_SUCCESS + + RetryHelperThinLock: + jmp RetryThinLock + + FramedLockHelper: + mov rdx, r10 + MON_EXIT_EPILOG_ADJUST_STACK + ; void JITutil_MonExitWorker(Object* obj, BYTE* pbLockTaken) + jmp JITutil_MonExitWorker + + LastRecursion: +ifdef MON_DEBUG +ifdef TRACK_SYNC + mov rax, [rdx + OFFSETOF__AwareLock__m_HoldingThread] +endif +endif + + sub dword ptr [r11 + OFFSETOF__Thread__m_dwLockCount], 1 + mov qword ptr [rdx + OFFSETOF__AwareLock__m_HoldingThread], 0 + + Retry: + mov eax, dword ptr [rdx + OFFSETOF__AwareLock__m_MonitorHeld] + lea r9d, [eax - 1] + lock cmpxchg dword ptr [rdx + OFFSETOF__AwareLock__m_MonitorHeld], r9d + jne RetryHelper + + test eax, STATE_CHECK + jne MustSignal + + ; Done, leave and set pbLockTaken if we have it + MON_EXIT_RETURN_SUCCESS + + MustSignal: + mov rcx, rdx + mov rdx, r10 + MON_EXIT_EPILOG_ADJUST_STACK + ; void JITutil_MonSignal(AwareLock* lock, BYTE* pbLockTaken) + jmp JITutil_MonSignal + + RetryHelper: + jmp Retry + + LockNotTaken: + MON_EXIT_EPILOG_ADJUST_STACK + REPRET +NESTED_END JIT_MonExitWorker_InlineGetThread, _TEXT + + +; This is a frameless helper for trying to enter a monitor on a object. +; The object is in ARGUMENT_REG1 and a timeout in ARGUMENT_REG2. This tries the +; normal case (no object allocation) in line and calls a framed helper for the +; other cases. +; +; void JIT_MonTryEnter_InlineGetThread(Object* obj, INT32 timeOut, BYTE* pbLockTaken) +NESTED_ENTRY JIT_MonTryEnter_InlineGetThread, _TEXT + ; save rcx, rdx (timeout) in the shadow space + .savereg rcx, 8h + mov [rsp + 8h], rcx + .savereg rdx, 10h + mov [rsp + 10h], rdx +ifdef MON_DEBUG +ifdef TRACK_SYNC + alloc_stack MON_ENTER_STACK_SIZE_INLINEGETTHREAD + +; rcx has already been saved +; save_reg_postrsp rcx, MON_ENTER_STACK_SIZE_INLINEGETTHREAD + 8h + 0h +; rdx has already been saved +; save_reg_postrsp rdx, MON_ENTER_STACK_SIZE + 8h + 8h + save_reg_postrsp r8, MON_ENTER_STACK_SIZE_INLINEGETTHREAD + 8h + 10h + save_reg_postrsp r9, MON_ENTER_STACK_SIZE_INLINEGETTHREAD + 8h + 18h +endif +endif + END_PROLOGUE + + ; Check if the instance is NULL + test rcx, rcx + jz FramedLockHelper + + ; Check if the timeout looks valid + cmp edx, -1 + jl FramedLockHelper + + PATCHABLE_INLINE_GETTHREAD r11, JIT_MonTryEnter_GetThread_PatchLabel + + ; Initialize delay value for retry with exponential backoff + mov r10d, dword ptr [g_SpinConstants + OFFSETOF__g_SpinConstants__dwInitialDuration] + + ; Check if we can abort here + mov eax, dword ptr [r11 + OFFSETOF__Thread__m_State] + and eax, THREAD_CATCHATSAFEPOINT_BITS + ; Go through the slow code path to initiate THreadAbort + jnz FramedLockHelper + + ; r9 will hold the syncblockindex address + lea r9, [rcx - OFFSETOF__ObjHeader__SyncBlkIndex] + + RetryThinLock: + ; Fetch the syncblock dword + mov eax, dword ptr [r9] + + ; Check whether we have the "thin lock" layout, the lock is free and the spin lock bit is not set + test eax, BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX + BIT_SBLK_SPIN_LOCK + SBLK_MASK_LOCK_THREADID + SBLK_MASK_LOCK_RECLEVEL + jne NeedMoreTests + + ; Everything is fine - get the thread id to store in the lock + mov edx, dword ptr [r11 + OFFSETOF__Thread__m_ThreadId] + + ; If the thread id is too large, we need a syncblock for sure + cmp edx, SBLK_MASK_LOCK_THREADID + ja FramedLockHelper + + ; We want to store a new value with the current thread id set in the low 10 bits + or edx, eax + lock cmpxchg dword ptr [r9], edx + jnz RetryHelperThinLock + + ; Got the lock, everything is fine + add dword ptr [r11 + OFFSETOF__Thread__m_dwLockCount], 1 + ; Return TRUE + mov byte ptr [r8], 1 +ifdef MON_DEBUG +ifdef TRACK_SYNC + add rsp, MON_ENTER_STACK_SIZE_INLINEGETTHREAD +endif +endif + ret + + NeedMoreTests: + ; OK, not the simple case, find out which case it is + test eax, BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX + jnz HaveHashOrSyncBlockIndex + + ; The header is transitioning or the lock + test eax, BIT_SBLK_SPIN_LOCK + jnz RetryHelperThinLock + + ; Here we know we have the "thin lock" layout, but the lock is not free. + ; It could still be the recursion case, compare the thread id to check + mov edx, eax + and edx, SBLK_MASK_LOCK_THREADID + cmp edx, dword ptr [r11 + OFFSETOF__Thread__m_ThreadId] + jne PrepareToWaitThinLock + + ; Ok, the thread id matches, it's the recursion case. + ; Dump up the recursion level and check for overflow + lea edx, [eax + SBLK_LOCK_RECLEVEL_INC] + test edx, SBLK_MASK_LOCK_RECLEVEL + jz FramedLockHelper + + ; Try to put the new recursion level back. If the header was changed in the meantime + ; we need a full retry, because the layout could have changed + lock cmpxchg dword ptr [r9], edx + jnz RetryHelperThinLock + + ; Everything went fine and we're done, return TRUE + mov byte ptr [r8], 1 +ifdef MON_DEBUG +ifdef TRACK_SYNC + add rsp, MON_ENTER_STACK_SIZE_INLINEGETTHREAD +endif +endif + ret + + PrepareToWaitThinLock: + ; Return failure if timeout is zero + cmp dword ptr [rsp + 10h], 0 + je TimeoutZero + + ; If we are on an MP system, we try spinning for a certain number of iterations + cmp dword ptr [g_SystemInfo + OFFSETOF__g_SystemInfo__dwNumberOfProcessors], 1 + jle FramedLockHelper + + ; Exponential backoff; delay by approximately 2*r10d clock cycles + mov eax, r10d + DelayLoopThinLock: + pause ; indicate to the CPU that we are spin waiting + sub eax, 1 + jnz DelayLoopThinLock + + ; Next time, wait a factor longer + imul r10d, dword ptr [g_SpinConstants + OFFSETOF__g_SpinConstants__dwBackoffFactor] + + cmp r10d, dword ptr [g_SpinConstants + OFFSETOF__g_SpinConstants__dwMaximumDuration] + jle RetryHelperThinLock + + jmp FramedLockHelper + + RetryHelperThinLock: + jmp RetryThinLock + + TimeoutZero: + ; Did not acquire, return FALSE + mov byte ptr [r8], 0 +ifdef MON_DEBUG +ifdef TRACK_SYNC + add rsp, MON_ENTER_STACK_SIZE_INLINEGETTHREAD +endif +endif + ret + + HaveHashOrSyncBlockIndex: + ; If we have a hash code already, we need to create a sync block + test eax, BIT_SBLK_IS_HASHCODE + jnz FramedLockHelper + + ; OK, we have a sync block index, just and out the top bits and grab the synblock index + and eax, MASK_SYNCBLOCKINDEX + + ; Get the sync block pointer + mov rdx, qword ptr [g_pSyncTable] + shl eax, 4 + mov rdx, [rdx + rax + OFFSETOF__SyncTableEntry__m_SyncBlock] + + ; Check if the sync block has been allocated + test rdx, rdx + jz FramedLockHelper + + ; Get a pointer to the lock object + lea rdx, [rdx + OFFSETOF__SyncBlock__m_Monitor] + + RetrySyncBlock: + ; Attempt to acuire the lock + mov eax, dword ptr [rdx + OFFSETOF__AwareLock__m_MonitorHeld] + test eax, eax + jne HaveWaiters + + ; Common case, lock isn't held and there are no waiters. Attempt to + ; gain ownership ourselves + xor ecx, ecx + inc ecx + lock cmpxchg dword ptr [rdx + OFFSETOF__AwareLock__m_MonitorHeld], ecx + jnz RetryHelperSyncBlock + + ; Success. Save the thread object in the lock and increment the use count + mov qword ptr [rdx + OFFSETOF__AwareLock__m_HoldingThread], r11 + add dword ptr [rdx + OFFSETOF__AwareLock__m_Recursion], 1 + add dword ptr [r11 + OFFSETOF__Thread__m_dwLockCount], 1 + +ifdef MON_DEBUG +ifdef TRACK_SYNC + mov rcx, [rsp + MON_ENTER_STACK_SIZE_INLINEGETTHREAD] ; return address + ; void EnterSyncHelper(UINT_PTR caller, AwareLock* lock) + call EnterSyncHelper +endif +endif + + ; Return TRUE + mov byte ptr [r8], 1 +ifdef MON_DEBUG +ifdef TRACK_SYNC + add rsp, MON_ENTER_STACK_SIZE_INLINEGETTHREAD +endif +endif + ret + + ; It's possible to get here with waiters by no lock held, but in this + ; case a signal is about to be fired which will wake up the waiter. So + ; for fairness sake we should wait too. + ; Check first for recur11ve lock attempts on the same thread. + HaveWaiters: + ; Is mutex already owned by current thread? + cmp [rdx + OFFSETOF__AwareLock__m_HoldingThread], r11 + jne PrepareToWait + + ; Yes, bump our use count. + add dword ptr [rdx + OFFSETOF__AwareLock__m_Recursion], 1 + +ifdef MON_DEBUG +ifdef TRACK_SYNC + mov rcx, [rsp + MON_ENTER_STACK_SIZE_INLINEGETTHREAD] ; return address + ; void EnterSyncHelper(UINT_PTR caller, AwareLock* lock) + call EnterSyncHelper +endif +endif + + ; Return TRUE + mov byte ptr [r8], 1 +ifdef MON_DEBUG +ifdef TRACK_SYNC + add rsp, MON_ENTER_STACK_SIZE_INLINEGETTHREAD +endif +endif + ret + + PrepareToWait: + ; Return failure if timeout is zero + cmp dword ptr [rsp + 10h], 0 +ifdef MON_DEBUG +ifdef TRACK_SYNC + ; if we are using the _DEBUG stuff then rsp has been adjusted + ; so compare the value at the adjusted position + ; there's really little harm in the extra stack read + cmp dword ptr [rsp + MON_ENTER_STACK_SIZE_INLINEGETTHREAD + 10h] +endif +endif + je TimeoutZero + + ; If we are on an MP system, we try spinning for a certain number of iterations + cmp dword ptr [g_SystemInfo + OFFSETOF__g_SystemInfo__dwNumberOfProcessors], 1 + jle Block + + ; Exponential backoff; delay by approximately 2*r10d clock cycles + mov eax, r10d + DelayLoop: + pause ; indicate to the CPU that we are spin waiting + sub eax, 1 + jnz DelayLoop + + ; Next time, wait a factor longer + imul r10d, dword ptr [g_SpinConstants + OFFSETOF__g_SpinConstants__dwBackoffFactor] + + cmp r10d, dword ptr [g_SpinConstants + OFFSETOF__g_SpinConstants__dwMaximumDuration] + jle RetrySyncBlock + + jmp Block + + RetryHelperSyncBlock: + jmp RetrySyncBlock + + Block: + ; In the Block case we've trashed RCX, restore it + mov rcx, [rsp + 8h] +ifdef MON_DEBUG +ifdef TRACK_SYNC + ; if we're tracking this stuff then rcx is at a different offset to RSP, we just + ; overwrite the wrong value which we just got... this is for debug purposes only + ; so there's really no performance issue here + mov rcx, [rsp + MON_ENTER_STACK_SIZE_INLINEGETTHREAD + 8h] +endif +endif + FramedLockHelper: +ifdef MON_DEBUG +ifdef TRACK_SYNC + add rsp, MON_ENTER_STACK_SIZE_INLINEGETTHREAD +endif +endif + mov rdx, [rsp + 10h] + ; void JITutil_MonTryEnter(Object* obj, INT32 timeout) + jmp JITutil_MonTryEnter + +NESTED_END JIT_MonTryEnter_InlineGetThread, _TEXT + + +MON_ENTER_STATIC_RETURN_SUCCESS macro + ; pbLockTaken is never null for static helpers + test rdx, rdx + mov byte ptr [rdx], 1 + REPRET + + endm + +MON_EXIT_STATIC_RETURN_SUCCESS macro + ; pbLockTaken is never null for static helpers + mov byte ptr [rdx], 0 + REPRET + + endm + + +; This is a frameless helper for entering a static monitor on a class. +; The methoddesc is in ARGUMENT_REG1. This tries the normal case (no +; blocking or object allocation) in line and calls a framed helper +; for the other cases. +; +; void JIT_MonEnterStatic_InlineGetThread(AwareLock *lock, BYTE *pbLockTaken) +NESTED_ENTRY JIT_MonEnterStatic_InlineGetThread, _TEXT + .savereg rcx, 0 +ifdef MON_DEBUG +ifdef TRACK_SYNC + alloc_stack MIN_SIZE + save_reg_postrsp rcx, MIN_SIZE + 8h + 0h +endif +endif + END_PROLOGUE + + ; Attempt to acquire the lock + Retry: + mov eax, dword ptr [rcx + OFFSETOF__AwareLock__m_MonitorHeld] + test eax, eax + jne HaveWaiters + + ; Common case; lock isn't held and there are no waiters. Attempt to + ; gain ownership by ourselves. + mov r10d, 1 + + lock cmpxchg dword ptr [rcx + OFFSETOF__AwareLock__m_MonitorHeld], r10d + jnz RetryHelper + + PATCHABLE_INLINE_GETTHREAD rax, JIT_MonEnterStaticWorker_InlineGetThread_GetThread_PatchLabel_1 + + mov qword ptr [rcx + OFFSETOF__AwareLock__m_HoldingThread], rax + add dword ptr [rcx + OFFSETOF__AwareLock__m_Recursion], 1 + add dword ptr [rax + OFFSETOF__Thread__m_dwLockCount], 1 + +ifdef MON_DEBUG +ifdef TRACK_SYNC + mov rdx, rcx + mov rcx, [rsp] + add rsp, MIN_SIZE + ; void EnterSyncHelper(UINT_PTR caller, AwareLock* lock) + jmp EnterSyncHelper +endif +endif + MON_ENTER_STATIC_RETURN_SUCCESS + + ; It's possible to get here with waiters by with no lock held, in this + ; case a signal is about to be fired which will wake up a waiter. So + ; for fairness sake we should wait too. + ; Check first for recursive lock attempts on the same thread. + HaveWaiters: + PATCHABLE_INLINE_GETTHREAD rax, JIT_MonEnterStaticWorker_InlineGetThread_GetThread_PatchLabel_2 + + ; Is mutex alread owned by current thread? + cmp [rcx + OFFSETOF__AwareLock__m_HoldingThread], rax + jne PrepareToWait + + ; Yes, bump our use count. + add dword ptr [rcx + OFFSETOF__AwareLock__m_Recursion], 1 +ifdef MON_DEBUG +ifdef TRACK_SYNC + mov rdx, rcx + mov rcx, [rsp + MIN_SIZE] + add rsp, MIN_SIZE + ; void EnterSyncHelper(UINT_PTR caller, AwareLock* lock) + jmp EnterSyncHelper +endif +endif + ret + + PrepareToWait: +ifdef MON_DEBUG +ifdef TRACK_SYNC + add rsp, MIN_SIZE +endif +endif + ; void JITutil_MonContention(AwareLock* obj, BYTE* pbLockTaken) + jmp JITutil_MonContention + + RetryHelper: + jmp Retry +NESTED_END JIT_MonEnterStatic_InlineGetThread, _TEXT + +; A frameless helper for exiting a static monitor on a class. +; The methoddesc is in ARGUMENT_REG1. This tries the normal case (no +; blocking or object allocation) in line and calls a framed helper +; for the other cases. +; +; void JIT_MonExitStatic_InlineGetThread(AwareLock *lock, BYTE *pbLockTaken) +NESTED_ENTRY JIT_MonExitStatic_InlineGetThread, _TEXT + .savereg rcx, 0 +ifdef MON_DEBUG +ifdef TRACK_SYNC + alloc_stack MIN_SIZE + save_reg_postrsp rcx, MIN_SIZE + 8h + 0h +endif +endif + END_PROLOGUE + +ifdef MON_DEBUG +ifdef TRACK_SYNC + push rsi + push rdi + mov rsi, rcx + mov rdi, rdx + mov rdx, [rsp + 8] + call LeaveSyncHelper + mov rcx, rsi + mov rdx, rdi + pop rdi + pop rsi +endif +endif + PATCHABLE_INLINE_GETTHREAD rax, JIT_MonExitStaticWorker_InlineGetThread_GetThread_PatchLabel + + ; Check if lock is held + cmp [rcx + OFFSETOF__AwareLock__m_HoldingThread], rax + jne LockError + + ; Reduce our recursion count + sub dword ptr [rcx + OFFSETOF__AwareLock__m_Recursion], 1 + jz LastRecursion + +ifdef MON_DEBUG +ifdef TRACK_SYNC + add rsp, MIN_SIZE + ret +endif +endif + REPRET + + ; This is the last count we held on this lock, so release the lock + LastRecursion: + ; Thead* is in rax + sub dword ptr [rax + OFFSETOF__Thread__m_dwLockCount], 1 + mov qword ptr [rcx + OFFSETOF__AwareLock__m_HoldingThread], 0 + + Retry: + mov eax, dword ptr [rcx + OFFSETOF__AwareLock__m_MonitorHeld] + lea r10d, [eax - 1] + lock cmpxchg dword ptr [rcx + OFFSETOF__AwareLock__m_MonitorHeld], r10d + jne RetryHelper + test eax, STATE_CHECK + jne MustSignal + +ifdef MON_DEBUG +ifdef TRACK_SYNC + add rsp, MIN_SIZE + ret +endif +endif + MON_EXIT_STATIC_RETURN_SUCCESS + + MustSignal: +ifdef MON_DEBUG +ifdef TRACK_SYNC + add rsp, MIN_SIZE +endif +endif + ; void JITutil_MonSignal(AwareLock* lock, BYTE* pbLockTaken) + jmp JITutil_MonSignal + + RetryHelper: + jmp Retry + + LockError: + mov rcx, CORINFO_SynchronizationLockException_ASM +ifdef MON_DEBUG +ifdef TRACK_SYNC + add rsp, MIN_SIZE +endif +endif + ; void JIT_InternalThrow(unsigned exceptNum) + jmp JIT_InternalThrow +NESTED_END JIT_MonExitStatic_InlineGetThread, _TEXT + + end + diff --git a/src/vm/amd64/JitHelpers_Slow.asm b/src/vm/amd64/JitHelpers_Slow.asm new file mode 100644 index 0000000000..7deed49d98 --- /dev/null +++ b/src/vm/amd64/JitHelpers_Slow.asm @@ -0,0 +1,1830 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; ==++== +; + +; +; ==--== +; *********************************************************************** +; File: JitHelpers_Slow.asm, see history in jithelp.asm +; +; Notes: These are ASM routinues which we believe to be cold in normal +; AMD64 scenarios, mainly because they have other versions which +; have some more performant nature which will be used in the best +; cases. +; *********************************************************************** + +include AsmMacros.inc +include asmconstants.inc + +; Min amount of stack space that a nested function should allocate. +MIN_SIZE equ 28h + +EXTERN g_ephemeral_low:QWORD +EXTERN g_ephemeral_high:QWORD +EXTERN g_lowest_address:QWORD +EXTERN g_highest_address:QWORD +EXTERN g_card_table:QWORD + +ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP +EXTERN g_sw_ww_table:QWORD +EXTERN g_sw_ww_enabled_for_gc_heap:BYTE +endif + +ifdef WRITE_BARRIER_CHECK +; Those global variables are always defined, but should be 0 for Server GC +g_GCShadow TEXTEQU +g_GCShadowEnd TEXTEQU +EXTERN g_GCShadow:QWORD +EXTERN g_GCShadowEnd:QWORD +endif + +JIT_NEW equ ?JIT_New@@YAPEAVObject@@PEAUCORINFO_CLASS_STRUCT_@@@Z +Object__DEBUG_SetAppDomain equ ?DEBUG_SetAppDomain@Object@@QEAAXPEAVAppDomain@@@Z +CopyValueClassUnchecked equ ?CopyValueClassUnchecked@@YAXPEAX0PEAVMethodTable@@@Z +JIT_Box equ ?JIT_Box@@YAPEAVObject@@PEAUCORINFO_CLASS_STRUCT_@@PEAX@Z +g_pStringClass equ ?g_pStringClass@@3PEAVMethodTable@@EA +FramedAllocateString equ ?FramedAllocateString@@YAPEAVStringObject@@K@Z +JIT_NewArr1 equ ?JIT_NewArr1@@YAPEAVObject@@PEAUCORINFO_CLASS_STRUCT_@@_J@Z + +INVALIDGCVALUE equ 0CCCCCCCDh + +extern JIT_NEW:proc +extern CopyValueClassUnchecked:proc +extern JIT_Box:proc +extern g_pStringClass:QWORD +extern FramedAllocateString:proc +extern JIT_NewArr1:proc + +extern JIT_GetSharedNonGCStaticBase_Helper:proc +extern JIT_GetSharedGCStaticBase_Helper:proc + +extern JIT_InternalThrow:proc + +ifdef _DEBUG +; Version for when we're sure to be in the GC, checks whether or not the card +; needs to be updated +; +; void JIT_WriteBarrier_Debug(Object** dst, Object* src) +LEAF_ENTRY JIT_WriteBarrier_Debug, _TEXT + +ifdef WRITE_BARRIER_CHECK + ; **ALSO update the shadow GC heap if that is enabled** + ; Do not perform the work if g_GCShadow is 0 + cmp g_GCShadow, 0 + je NoShadow + + ; If we end up outside of the heap don't corrupt random memory + mov r10, rcx + sub r10, [g_lowest_address] + jb NoShadow + + ; Check that our adjusted destination is somewhere in the shadow gc + add r10, [g_GCShadow] + cmp r10, [g_GCShadowEnd] + ja NoShadow + + ; Write ref into real GC; see comment below about possibility of AV + mov [rcx], rdx + ; Write ref into shadow GC + mov [r10], rdx + + ; Ensure that the write to the shadow heap occurs before the read from + ; the GC heap so that race conditions are caught by INVALIDGCVALUE + mfence + + ; Check that GC/ShadowGC values match + mov r11, [rcx] + mov rax, [r10] + cmp rax, r11 + je DoneShadow + mov r11, INVALIDGCVALUE + mov [r10], r11 + + jmp DoneShadow + + ; If we don't have a shadow GC we won't have done the write yet + NoShadow: +endif + + mov rax, rdx + + ; Do the move. It is correct to possibly take an AV here, the EH code + ; figures out that this came from a WriteBarrier and correctly maps it back + ; to the managed method which called the WriteBarrier (see setup in + ; InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rcx], rax + +ifdef WRITE_BARRIER_CHECK + ; If we had a shadow GC then we already wrote to the real GC at the same time + ; as the shadow GC so we want to jump over the real write immediately above + DoneShadow: +endif + +ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + ; Update the write watch table if necessary + cmp byte ptr [g_sw_ww_enabled_for_gc_heap], 0h + je CheckCardTable + mov r10, rcx + shr r10, 0Ch ; SoftwareWriteWatch::AddressToTableByteIndexShift + add r10, qword ptr [g_sw_ww_table] + cmp byte ptr [r10], 0h + jne CheckCardTable + mov byte ptr [r10], 0FFh +endif + + CheckCardTable: + ; See if we can just quick out + cmp rax, [g_ephemeral_low] + jb Exit + cmp rax, [g_ephemeral_high] + jnb Exit + + ; Check if we need to update the card table + ; Calc pCardByte + shr rcx, 0Bh + add rcx, [g_card_table] + + ; Check if this card is dirty + cmp byte ptr [rcx], 0FFh + jne UpdateCardTable + REPRET + + UpdateCardTable: + mov byte ptr [rcx], 0FFh + ret + + align 16 + Exit: + REPRET +LEAF_END_MARKED JIT_WriteBarrier_Debug, _TEXT +endif + +NESTED_ENTRY JIT_TrialAllocSFastMP, _TEXT + alloc_stack MIN_SIZE + END_PROLOGUE + + CALL_GETTHREAD + mov r11, rax + + mov r8d, [rcx + OFFSET__MethodTable__m_BaseSize] + + ; m_BaseSize is guaranteed to be a multiple of 8. + + mov r10, [r11 + OFFSET__Thread__m_alloc_context__alloc_limit] + mov rax, [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr] + + add r8, rax + + cmp r8, r10 + ja AllocFailed + + mov [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr], r8 + mov [rax], rcx + +ifdef _DEBUG + call DEBUG_TrialAllocSetAppDomain +endif ; _DEBUG + + ; epilog + add rsp, MIN_SIZE + ret + + AllocFailed: + add rsp, MIN_SIZE + jmp JIT_NEW +NESTED_END JIT_TrialAllocSFastMP, _TEXT + + +; HCIMPL2(Object*, JIT_Box, CORINFO_CLASS_HANDLE type, void* unboxedData) +NESTED_ENTRY JIT_BoxFastMP, _TEXT + alloc_stack MIN_SIZE + END_PROLOGUE + + mov rax, [rcx + OFFSETOF__MethodTable__m_pWriteableData] + + ; Check whether the class has not been initialized + test dword ptr [rax + OFFSETOF__MethodTableWriteableData__m_dwFlags], MethodTableWriteableData__enum_flag_Unrestored + jnz ClassNotInited + + CALL_GETTHREAD + mov r11, rax + + mov r8d, [rcx + OFFSET__MethodTable__m_BaseSize] + + ; m_BaseSize is guaranteed to be a multiple of 8. + + mov r10, [r11 + OFFSET__Thread__m_alloc_context__alloc_limit] + mov rax, [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr] + + add r8, rax + + cmp r8, r10 + ja AllocFailed + + mov [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr], r8 + mov [rax], rcx + +ifdef _DEBUG + call DEBUG_TrialAllocSetAppDomain +endif ; _DEBUG + + ; Check whether the object contains pointers + test dword ptr [rcx + OFFSETOF__MethodTable__m_dwFlags], MethodTable__enum_flag_ContainsPointers + jnz ContainsPointers + + ; We have no pointers - emit a simple inline copy loop + + mov ecx, [rcx + OFFSET__MethodTable__m_BaseSize] + sub ecx, 18h ; sizeof(ObjHeader) + sizeof(Object) + last slot + + CopyLoop: + mov r8, [rdx+rcx] + mov [rax+rcx+8], r8 + + sub ecx, 8 + jge CopyLoop + + add rsp, MIN_SIZE + ret + + ContainsPointers: + ; Do call to CopyValueClassUnchecked(object, data, pMT) + + mov [rsp+20h], rax + + mov r8, rcx + lea rcx, [rax + 8] + call CopyValueClassUnchecked + + mov rax, [rsp+20h] + + add rsp, MIN_SIZE + ret + + ClassNotInited: + AllocFailed: + add rsp, MIN_SIZE + jmp JIT_Box +NESTED_END JIT_BoxFastMP, _TEXT + + +NESTED_ENTRY AllocateStringFastMP, _TEXT + alloc_stack MIN_SIZE + END_PROLOGUE + + ; Instead of doing elaborate overflow checks, we just limit the number of elements + ; to (LARGE_OBJECT_SIZE - 256)/sizeof(WCHAR) or less. + ; This will avoid all overflow problems, as well as making sure + ; big string objects are correctly allocated in the big object heap. + + cmp ecx, (ASM_LARGE_OBJECT_SIZE - 256)/2 + jae OversizedString + + CALL_GETTHREAD + mov r11, rax + + mov rdx, [g_pStringClass] + mov r8d, [rdx + OFFSET__MethodTable__m_BaseSize] + + ; Calculate the final size to allocate. + ; We need to calculate baseSize + cnt*2, then round that up by adding 7 and anding ~7. + + lea r8d, [r8d + ecx*2 + 7] + and r8d, -8 + + mov r10, [r11 + OFFSET__Thread__m_alloc_context__alloc_limit] + mov rax, [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr] + + add r8, rax + + cmp r8, r10 + ja AllocFailed + + mov [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr], r8 + mov [rax], rdx + + mov [rax + OFFSETOF__StringObject__m_StringLength], ecx + +ifdef _DEBUG + call DEBUG_TrialAllocSetAppDomain +endif ; _DEBUG + + add rsp, MIN_SIZE + ret + + OversizedString: + AllocFailed: + add rsp, MIN_SIZE + jmp FramedAllocateString +NESTED_END AllocateStringFastMP, _TEXT + +FIX_INDIRECTION macro Reg +ifdef FEATURE_PREJIT + test Reg, 1 + jz @F + mov Reg, [Reg-1] + @@: +endif +endm + +; HCIMPL2(Object*, JIT_NewArr1, CORINFO_CLASS_HANDLE arrayTypeHnd_, INT_PTR size) +NESTED_ENTRY JIT_NewArr1VC_MP, _TEXT + alloc_stack MIN_SIZE + END_PROLOGUE + + ; We were passed a type descriptor in RCX, which contains the (shared) + ; array method table and the element type. + + ; The element count is in RDX + + ; NOTE: if this code is ported for CORINFO_HELP_NEWSFAST_ALIGN8, it will need + ; to emulate the double-specific behavior of JIT_TrialAlloc::GenAllocArray. + + ; Do a conservative check here. This is to avoid overflow while doing the calculations. We don't + ; have to worry about "large" objects, since the allocation quantum is never big enough for + ; LARGE_OBJECT_SIZE. + + ; For Value Classes, this needs to be 2^16 - slack (2^32 / max component size), + ; The slack includes the size for the array header and round-up ; for alignment. Use 256 for the + ; slack value out of laziness. + + ; In both cases we do a final overflow check after adding to the alloc_ptr. + + CALL_GETTHREAD + mov r11, rax + + ; we need to load the true method table from the type desc + mov r9, [rcx + OFFSETOF__ArrayTypeDesc__m_TemplateMT - 2] + + FIX_INDIRECTION r9 + + cmp rdx, (65535 - 256) + jae OversizedArray + + movzx r8d, word ptr [r9 + OFFSETOF__MethodTable__m_dwFlags] ; component size is low 16 bits + imul r8d, edx ; signed mul, but won't overflow due to length restriction above + add r8d, dword ptr [r9 + OFFSET__MethodTable__m_BaseSize] + + ; round the size to a multiple of 8 + + add r8d, 7 + and r8d, -8 + + mov r10, [r11 + OFFSET__Thread__m_alloc_context__alloc_limit] + mov rax, [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr] + + add r8, rax + jc AllocFailed + + cmp r8, r10 + ja AllocFailed + + mov [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr], r8 + mov [rax], r9 + + mov dword ptr [rax + OFFSETOF__ArrayBase__m_NumComponents], edx + +ifdef _DEBUG + call DEBUG_TrialAllocSetAppDomain +endif ; _DEBUG + + add rsp, MIN_SIZE + ret + + OversizedArray: + AllocFailed: + add rsp, MIN_SIZE + jmp JIT_NewArr1 +NESTED_END JIT_NewArr1VC_MP, _TEXT + + +; HCIMPL2(Object*, JIT_NewArr1, CORINFO_CLASS_HANDLE arrayTypeHnd_, INT_PTR size) +NESTED_ENTRY JIT_NewArr1OBJ_MP, _TEXT + alloc_stack MIN_SIZE + END_PROLOGUE + + ; We were passed a type descriptor in RCX, which contains the (shared) + ; array method table and the element type. + + ; The element count is in RDX + + ; NOTE: if this code is ported for CORINFO_HELP_NEWSFAST_ALIGN8, it will need + ; to emulate the double-specific behavior of JIT_TrialAlloc::GenAllocArray. + + ; Verifies that LARGE_OBJECT_SIZE fits in 32-bit. This allows us to do array size + ; arithmetic using 32-bit registers. + .erre ASM_LARGE_OBJECT_SIZE lt 100000000h + + cmp rdx, (ASM_LARGE_OBJECT_SIZE - 256)/8 + jae OversizedArray + + CALL_GETTHREAD + mov r11, rax + + ; we need to load the true method table from the type desc + mov r9, [rcx + OFFSETOF__ArrayTypeDesc__m_TemplateMT - 2] + + FIX_INDIRECTION r9 + + ; In this case we know the element size is sizeof(void *), or 8 for x64 + ; This helps us in two ways - we can shift instead of multiplying, and + ; there's no need to align the size either + + mov r8d, dword ptr [r9 + OFFSET__MethodTable__m_BaseSize] + lea r8d, [r8d + edx * 8] + + ; No need for rounding in this case - element size is 8, and m_BaseSize is guaranteed + ; to be a multiple of 8. + + mov r10, [r11 + OFFSET__Thread__m_alloc_context__alloc_limit] + mov rax, [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr] + + add r8, rax + + cmp r8, r10 + ja AllocFailed + + mov [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr], r8 + mov [rax], r9 + + mov dword ptr [rax + OFFSETOF__ArrayBase__m_NumComponents], edx + +ifdef _DEBUG + call DEBUG_TrialAllocSetAppDomain +endif ; _DEBUG + + add rsp, MIN_SIZE + ret + + OversizedArray: + AllocFailed: + add rsp, MIN_SIZE + jmp JIT_NewArr1 +NESTED_END JIT_NewArr1OBJ_MP, _TEXT + + + +; this m_GCLock should be a size_t so we don't have a store-forwarding penalty in the code below. +; Unfortunately, the compiler intrinsic for InterlockedExchangePointer seems to be broken and we +; get bad code gen in gc.cpp on IA64. + +M_GCLOCK equ ?m_GCLock@@3HC +extern M_GCLOCK:dword +extern generation_table:qword + +LEAF_ENTRY JIT_TrialAllocSFastSP, _TEXT + + mov r8d, [rcx + OFFSET__MethodTable__m_BaseSize] + + ; m_BaseSize is guaranteed to be a multiple of 8. + + inc [M_GCLOCK] + jnz JIT_NEW + + mov rax, [generation_table + 0] ; alloc_ptr + mov r10, [generation_table + 8] ; limit_ptr + + add r8, rax + + cmp r8, r10 + ja AllocFailed + + mov qword ptr [generation_table + 0], r8 ; update the alloc ptr + mov [rax], rcx + mov [M_GCLOCK], -1 + +ifdef _DEBUG + call DEBUG_TrialAllocSetAppDomain_NoScratchArea +endif ; _DEBUG + + ret + + AllocFailed: + mov [M_GCLOCK], -1 + jmp JIT_NEW +LEAF_END JIT_TrialAllocSFastSP, _TEXT + +; HCIMPL2(Object*, JIT_Box, CORINFO_CLASS_HANDLE type, void* unboxedData) +NESTED_ENTRY JIT_BoxFastUP, _TEXT + + mov rax, [rcx + OFFSETOF__MethodTable__m_pWriteableData] + + ; Check whether the class has not been initialized + test dword ptr [rax + OFFSETOF__MethodTableWriteableData__m_dwFlags], MethodTableWriteableData__enum_flag_Unrestored + jnz JIT_Box + + mov r8d, [rcx + OFFSET__MethodTable__m_BaseSize] + + ; m_BaseSize is guaranteed to be a multiple of 8. + + inc [M_GCLOCK] + jnz JIT_Box + + mov rax, [generation_table + 0] ; alloc_ptr + mov r10, [generation_table + 8] ; limit_ptr + + add r8, rax + + cmp r8, r10 + ja NoAlloc + + + mov qword ptr [generation_table + 0], r8 ; update the alloc ptr + mov [rax], rcx + mov [M_GCLOCK], -1 + +ifdef _DEBUG + call DEBUG_TrialAllocSetAppDomain_NoScratchArea +endif ; _DEBUG + + ; Check whether the object contains pointers + test dword ptr [rcx + OFFSETOF__MethodTable__m_dwFlags], MethodTable__enum_flag_ContainsPointers + jnz ContainsPointers + + ; We have no pointers - emit a simple inline copy loop + + mov ecx, [rcx + OFFSET__MethodTable__m_BaseSize] + sub ecx, 18h ; sizeof(ObjHeader) + sizeof(Object) + last slot + + CopyLoop: + mov r8, [rdx+rcx] + mov [rax+rcx+8], r8 + + sub ecx, 8 + jge CopyLoop + REPRET + + ContainsPointers: + + ; Do call to CopyValueClassUnchecked(object, data, pMT) + + push_vol_reg rax + alloc_stack 20h + END_PROLOGUE + + mov r8, rcx + lea rcx, [rax + 8] + call CopyValueClassUnchecked + + add rsp, 20h + pop rax + ret + + NoAlloc: + mov [M_GCLOCK], -1 + jmp JIT_Box +NESTED_END JIT_BoxFastUP, _TEXT + +LEAF_ENTRY AllocateStringFastUP, _TEXT + + ; We were passed the number of characters in ECX + + ; we need to load the method table for string from the global + + mov r11, [g_pStringClass] + + ; Instead of doing elaborate overflow checks, we just limit the number of elements + ; to (LARGE_OBJECT_SIZE - 256)/sizeof(WCHAR) or less. + ; This will avoid all overflow problems, as well as making sure + ; big string objects are correctly allocated in the big object heap. + + cmp ecx, (ASM_LARGE_OBJECT_SIZE - 256)/2 + jae FramedAllocateString + + mov r8d, [r11 + OFFSET__MethodTable__m_BaseSize] + + ; Calculate the final size to allocate. + ; We need to calculate baseSize + cnt*2, then round that up by adding 7 and anding ~7. + + lea r8d, [r8d + ecx*2 + 7] + and r8d, -8 + + inc [M_GCLOCK] + jnz FramedAllocateString + + mov rax, [generation_table + 0] ; alloc_ptr + mov r10, [generation_table + 8] ; limit_ptr + + add r8, rax + + cmp r8, r10 + ja AllocFailed + + mov qword ptr [generation_table + 0], r8 ; update the alloc ptr + mov [rax], r11 + mov [M_GCLOCK], -1 + + mov [rax + OFFSETOF__StringObject__m_StringLength], ecx + +ifdef _DEBUG + call DEBUG_TrialAllocSetAppDomain_NoScratchArea +endif ; _DEBUG + + ret + + AllocFailed: + mov [M_GCLOCK], -1 + jmp FramedAllocateString +LEAF_END AllocateStringFastUP, _TEXT + +; HCIMPL2(Object*, JIT_NewArr1, CORINFO_CLASS_HANDLE arrayTypeHnd_, INT_PTR size) +LEAF_ENTRY JIT_NewArr1VC_UP, _TEXT + + ; We were passed a type descriptor in RCX, which contains the (shared) + ; array method table and the element type. + + ; The element count is in RDX + + ; NOTE: if this code is ported for CORINFO_HELP_NEWSFAST_ALIGN8, it will need + ; to emulate the double-specific behavior of JIT_TrialAlloc::GenAllocArray. + + ; Do a conservative check here. This is to avoid overflow while doing the calculations. We don't + ; have to worry about "large" objects, since the allocation quantum is never big enough for + ; LARGE_OBJECT_SIZE. + + ; For Value Classes, this needs to be 2^16 - slack (2^32 / max component size), + ; The slack includes the size for the array header and round-up ; for alignment. Use 256 for the + ; slack value out of laziness. + + ; In both cases we do a final overflow check after adding to the alloc_ptr. + + ; we need to load the true method table from the type desc + mov r9, [rcx + OFFSETOF__ArrayTypeDesc__m_TemplateMT - 2] + + FIX_INDIRECTION r9 + + cmp rdx, (65535 - 256) + jae JIT_NewArr1 + + movzx r8d, word ptr [r9 + OFFSETOF__MethodTable__m_dwFlags] ; component size is low 16 bits + imul r8d, edx ; signed mul, but won't overflow due to length restriction above + add r8d, dword ptr [r9 + OFFSET__MethodTable__m_BaseSize] + + ; round the size to a multiple of 8 + + add r8d, 7 + and r8d, -8 + + inc [M_GCLOCK] + jnz JIT_NewArr1 + + mov rax, [generation_table + 0] ; alloc_ptr + mov r10, [generation_table + 8] ; limit_ptr + + add r8, rax + jc AllocFailed + + cmp r8, r10 + ja AllocFailed + + mov qword ptr [generation_table + 0], r8 ; update the alloc ptr + mov [rax], r9 + mov [M_GCLOCK], -1 + + mov dword ptr [rax + OFFSETOF__ArrayBase__m_NumComponents], edx + +ifdef _DEBUG + call DEBUG_TrialAllocSetAppDomain_NoScratchArea +endif ; _DEBUG + + ret + + AllocFailed: + mov [M_GCLOCK], -1 + jmp JIT_NewArr1 +LEAF_END JIT_NewArr1VC_UP, _TEXT + + +; HCIMPL2(Object*, JIT_NewArr1, CORINFO_CLASS_HANDLE arrayTypeHnd_, INT_PTR size) +LEAF_ENTRY JIT_NewArr1OBJ_UP, _TEXT + + ; We were passed a type descriptor in RCX, which contains the (shared) + ; array method table and the element type. + + ; The element count is in RDX + + ; NOTE: if this code is ported for CORINFO_HELP_NEWSFAST_ALIGN8, it will need + ; to emulate the double-specific behavior of JIT_TrialAlloc::GenAllocArray. + + ; Verifies that LARGE_OBJECT_SIZE fits in 32-bit. This allows us to do array size + ; arithmetic using 32-bit registers. + .erre ASM_LARGE_OBJECT_SIZE lt 100000000h + + cmp rdx, (ASM_LARGE_OBJECT_SIZE - 256)/8 ; sizeof(void*) + jae OversizedArray + + ; we need to load the true method table from the type desc + mov r9, [rcx + OFFSETOF__ArrayTypeDesc__m_TemplateMT - 2] + + FIX_INDIRECTION r9 + + ; In this case we know the element size is sizeof(void *), or 8 for x64 + ; This helps us in two ways - we can shift instead of multiplying, and + ; there's no need to align the size either + + mov r8d, dword ptr [r9 + OFFSET__MethodTable__m_BaseSize] + lea r8d, [r8d + edx * 8] + + ; No need for rounding in this case - element size is 8, and m_BaseSize is guaranteed + ; to be a multiple of 8. + + inc [M_GCLOCK] + jnz JIT_NewArr1 + + mov rax, [generation_table + 0] ; alloc_ptr + mov r10, [generation_table + 8] ; limit_ptr + + add r8, rax + + cmp r8, r10 + ja AllocFailed + + mov qword ptr [generation_table + 0], r8 ; update the alloc ptr + mov [rax], r9 + mov [M_GCLOCK], -1 + + mov dword ptr [rax + OFFSETOF__ArrayBase__m_NumComponents], edx + +ifdef _DEBUG + call DEBUG_TrialAllocSetAppDomain_NoScratchArea +endif ; _DEBUG + + ret + + AllocFailed: + mov [M_GCLOCK], -1 + + OversizedArray: + jmp JIT_NewArr1 +LEAF_END JIT_NewArr1OBJ_UP, _TEXT + + +NESTED_ENTRY JIT_GetSharedNonGCStaticBase_Slow, _TEXT + alloc_stack MIN_SIZE + END_PROLOGUE + + ; Check if rcx (moduleDomainID) is not a moduleID + test rcx, 1 + jz HaveLocalModule + + CALL_GETAPPDOMAIN + + ; Get the LocalModule + mov rax, [rax + OFFSETOF__AppDomain__m_sDomainLocalBlock + OFFSETOF__DomainLocalBlock__m_pModuleSlots] + ; rcx will always be odd, so: rcx * 4 - 4 <=> (rcx >> 1) * 8 + mov rcx, [rax + rcx * 4 - 4] + + HaveLocalModule: + ; If class is not initialized, bail to C++ helper + test [rcx + OFFSETOF__DomainLocalModule__m_pDataBlob + rdx], 1 + jz CallHelper + + mov rax, rcx + add rsp, MIN_SIZE + ret + + align 16 + CallHelper: + ; Tail call Jit_GetSharedNonGCStaticBase_Helper + add rsp, MIN_SIZE + jmp JIT_GetSharedNonGCStaticBase_Helper +NESTED_END JIT_GetSharedNonGCStaticBase_Slow, _TEXT + +NESTED_ENTRY JIT_GetSharedNonGCStaticBaseNoCtor_Slow, _TEXT + alloc_stack MIN_SIZE + END_PROLOGUE + + ; Check if rcx (moduleDomainID) is not a moduleID + test rcx, 1 + jz HaveLocalModule + + CALL_GETAPPDOMAIN + + ; Get the LocalModule + mov rax, [rax + OFFSETOF__AppDomain__m_sDomainLocalBlock + OFFSETOF__DomainLocalBlock__m_pModuleSlots] + ; rcx will always be odd, so: rcx * 4 - 4 <=> (rcx >> 1) * 8 + mov rax, [rax + rcx * 4 - 4] + + add rsp, MIN_SIZE + ret + + align 16 + HaveLocalModule: + mov rax, rcx + add rsp, MIN_SIZE + ret +NESTED_END JIT_GetSharedNonGCStaticBaseNoCtor_Slow, _TEXT + +NESTED_ENTRY JIT_GetSharedGCStaticBase_Slow, _TEXT + alloc_stack MIN_SIZE + END_PROLOGUE + + ; Check if rcx (moduleDomainID) is not a moduleID + test rcx, 1 + jz HaveLocalModule + + CALL_GETAPPDOMAIN + + ; Get the LocalModule + mov rax, [rax + OFFSETOF__AppDomain__m_sDomainLocalBlock + OFFSETOF__DomainLocalBlock__m_pModuleSlots] + ; rcx will always be odd, so: rcx * 4 - 4 <=> (rcx >> 1) * 8 + mov rcx, [rax + rcx * 4 - 4] + + HaveLocalModule: + ; If class is not initialized, bail to C++ helper + test [rcx + OFFSETOF__DomainLocalModule__m_pDataBlob + rdx], 1 + jz CallHelper + + mov rax, [rcx + OFFSETOF__DomainLocalModule__m_pGCStatics] + + add rsp, MIN_SIZE + ret + + align 16 + CallHelper: + ; Tail call Jit_GetSharedGCStaticBase_Helper + add rsp, MIN_SIZE + jmp JIT_GetSharedGCStaticBase_Helper +NESTED_END JIT_GetSharedGCStaticBase_Slow, _TEXT + +NESTED_ENTRY JIT_GetSharedGCStaticBaseNoCtor_Slow, _TEXT + alloc_stack MIN_SIZE + END_PROLOGUE + + ; Check if rcx (moduleDomainID) is not a moduleID + test rcx, 1 + jz HaveLocalModule + + CALL_GETAPPDOMAIN + + ; Get the LocalModule + mov rax, [rax + OFFSETOF__AppDomain__m_sDomainLocalBlock + OFFSETOF__DomainLocalBlock__m_pModuleSlots] + ; rcx will always be odd, so: rcx * 4 - 4 <=> (rcx >> 1) * 8 + mov rcx, [rax + rcx * 4 - 4] + + HaveLocalModule: + mov rax, [rcx + OFFSETOF__DomainLocalModule__m_pGCStatics] + + add rsp, MIN_SIZE + ret +NESTED_END JIT_GetSharedGCStaticBaseNoCtor_Slow, _TEXT + + +MON_ENTER_STACK_SIZE equ 00000020h +MON_EXIT_STACK_SIZE equ 00000068h + +ifdef MON_DEBUG +ifdef TRACK_SYNC +MON_ENTER_STACK_SIZE_INLINEGETTHREAD equ 00000020h +MON_EXIT_STACK_SIZE_INLINEGETTHREAD equ 00000068h +endif +endif + +BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX equ 08000000h ; syncblk.h +BIT_SBLK_IS_HASHCODE equ 04000000h ; syncblk.h +BIT_SBLK_SPIN_LOCK equ 10000000h ; syncblk.h + +SBLK_MASK_LOCK_THREADID equ 000003FFh ; syncblk.h +SBLK_LOCK_RECLEVEL_INC equ 00000400h ; syncblk.h +SBLK_MASK_LOCK_RECLEVEL equ 0000FC00h ; syncblk.h + +MASK_SYNCBLOCKINDEX equ 03FFFFFFh ; syncblk.h +STATE_CHECK equ 0FFFFFFFEh + +MT_CTX_PROXY_FLAG equ 10000000h + +g_pSyncTable equ ?g_pSyncTable@@3PEAVSyncTableEntry@@EA +g_SystemInfo equ ?g_SystemInfo@@3U_SYSTEM_INFO@@A +g_SpinConstants equ ?g_SpinConstants@@3USpinConstants@@A + +extern g_pSyncTable:QWORD +extern g_SystemInfo:QWORD +extern g_SpinConstants:QWORD + +; JITutil_MonEnterWorker(Object* obj, BYTE* pbLockTaken) +extern JITutil_MonEnterWorker:proc +; JITutil_MonTryEnter(Object* obj, INT32 timeout, BYTE* pbLockTaken) +extern JITutil_MonTryEnter:proc +; JITutil_MonExitWorker(Object* obj, BYTE* pbLockTaken) +extern JITutil_MonExitWorker:proc +; JITutil_MonSignal(AwareLock* lock, BYTE* pbLockTaken) +extern JITutil_MonSignal:proc +; JITutil_MonContention(AwareLock* lock, BYTE* pbLockTaken) +extern JITutil_MonContention:proc + +ifdef _DEBUG +MON_DEBUG equ 1 +endif + +ifdef MON_DEBUG +ifdef TRACK_SYNC +extern EnterSyncHelper:proc +extern LeaveSyncHelper:proc +endif +endif + + +; This is a frameless helper for entering a monitor on a object. +; The object is in ARGUMENT_REG1. This tries the normal case (no +; blocking or object allocation) in line and calls a framed helper +; for the other cases. +; +; EXTERN_C void JIT_MonEnterWorker_Slow(Object* obj, /*OUT*/ BYTE* pbLockTaken) +NESTED_ENTRY JIT_MonEnterWorker_Slow, _TEXT + push_nonvol_reg rsi + + alloc_stack MON_ENTER_STACK_SIZE + + save_reg_postrsp rcx, MON_ENTER_STACK_SIZE + 10h + 0h + save_reg_postrsp rdx, MON_ENTER_STACK_SIZE + 10h + 8h + save_reg_postrsp r8, MON_ENTER_STACK_SIZE + 10h + 10h + save_reg_postrsp r9, MON_ENTER_STACK_SIZE + 10h + 18h + + END_PROLOGUE + + ; Check if the instance is NULL + test rcx, rcx + jz FramedLockHelper + + ; Put pbLockTaken in rsi, this can be null + mov rsi, rdx + + ; We store the thread object in r11 + CALL_GETTHREAD + mov r11, rax + + ; Initialize delay value for retry with exponential backoff + mov r10d, dword ptr [g_SpinConstants + OFFSETOF__g_SpinConstants__dwInitialDuration] + + ; Check if we can abort here + mov eax, dword ptr [r11 + OFFSETOF__Thread__m_State] + and eax, THREAD_CATCHATSAFEPOINT_BITS + ; Go through the slow code path to initiate ThreadAbort + jnz FramedLockHelper + + ; r8 will hold the syncblockindex address + lea r8, [rcx - OFFSETOF__ObjHeader__SyncBlkIndex] + + RetryThinLock: + ; Fetch the syncblock dword + mov eax, dword ptr [r8] + + ; Check whether we have the "thin lock" layout, the lock is free and the spin lock bit is not set + test eax, BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX + BIT_SBLK_SPIN_LOCK + SBLK_MASK_LOCK_THREADID + SBLK_MASK_LOCK_RECLEVEL + jnz NeedMoreTests + + ; Everything is fine - get the thread id to store in the lock + mov edx, dword ptr [r11 + OFFSETOF__Thread__m_ThreadId] + + ; If the thread id is too large, we need a syncblock for sure + cmp edx, SBLK_MASK_LOCK_THREADID + ja FramedLockHelper + + ; We want to store a new value with the current thread id set in the low 10 bits + or edx, eax + lock cmpxchg dword ptr [r8], edx + jnz PrepareToWaitThinLock + + ; Everything went fine and we're done + add dword ptr [r11 + OFFSETOF__Thread__m_dwLockCount], 1 + + ; Done, leave and set pbLockTaken if we have it + jmp LockTaken + + NeedMoreTests: + ; OK, not the simple case, find out which case it is + test eax, BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX + jnz HaveHashOrSyncBlockIndex + + ; The header is transitioning or the lock, treat this as if the lock was taken + test eax, BIT_SBLK_SPIN_LOCK + jnz PrepareToWaitThinLock + + ; Here we know we have the "thin lock" layout, but the lock is not free. + ; It could still be the recursion case, compare the thread id to check + mov edx, eax + and edx, SBLK_MASK_LOCK_THREADID + cmp edx, dword ptr [r11 + OFFSETOF__Thread__m_ThreadId] + jne PrepareToWaitThinLock + + ; Ok, the thread id matches, it's the recursion case. + ; Bump up the recursion level and check for overflow + lea edx, [eax + SBLK_LOCK_RECLEVEL_INC] + test edx, SBLK_MASK_LOCK_RECLEVEL + jz FramedLockHelper + + ; Try to put the new recursion level back. If the header was changed in the meantime + ; we need a full retry, because the layout could have changed + lock cmpxchg dword ptr [r8], edx + jnz RetryHelperThinLock + + ; Done, leave and set pbLockTaken if we have it + jmp LockTaken + + PrepareToWaitThinLock: + ; If we are on an MP system, we try spinning for a certain number of iterations + cmp dword ptr [g_SystemInfo + OFFSETOF__g_SystemInfo__dwNumberOfProcessors], 1 + jle FramedLockHelper + + ; Exponential backoff; delay by approximately 2*r10 clock cycles + mov eax, r10d + delayLoopThinLock: + pause ; indicate to the CPU that we are spin waiting + sub eax, 1 + jnz delayLoopThinLock + + ; Next time, wait a factor longer + imul r10d, dword ptr [g_SpinConstants + OFFSETOF__g_SpinConstants__dwBackoffFactor] + + cmp r10d, dword ptr [g_SpinConstants + OFFSETOF__g_SpinConstants__dwMaximumDuration] + jle RetryHelperThinLock + + jmp FramedLockHelper + + RetryHelperThinLock: + jmp RetryThinLock + + HaveHashOrSyncBlockIndex: + ; If we have a hash code already, we need to create a sync block + test eax, BIT_SBLK_IS_HASHCODE + jnz FramedLockHelper + + ; OK, we have a sync block index, just and out the top bits and grab the synblock index + and eax, MASK_SYNCBLOCKINDEX + + ; Get the sync block pointer + mov rdx, qword ptr [g_pSyncTable] + shl eax, 4h + mov rdx, [rdx + rax + OFFSETOF__SyncTableEntry__m_SyncBlock] + + ; Check if the sync block has been allocated + test rdx, rdx + jz FramedLockHelper + + ; Get a pointer to the lock object + lea rdx, [rdx + OFFSETOF__SyncBlock__m_Monitor] + + ; Attempt to acquire the lock + RetrySyncBlock: + mov eax, dword ptr [rdx + OFFSETOF__AwareLock__m_MonitorHeld] + test eax, eax + jne HaveWaiters + + ; Common case, lock isn't held and there are no waiters. Attempt to + ; gain ownership ourselves + xor ecx, ecx + inc ecx + lock cmpxchg dword ptr [rdx + OFFSETOF__AwareLock__m_MonitorHeld], ecx + jnz RetryHelperSyncBlock + + ; Success. Save the thread object in the lock and increment the use count + mov qword ptr [rdx + OFFSETOF__AwareLock__m_HoldingThread], r11 + add dword ptr [rdx + OFFSETOF__AwareLock__m_Recursion], 1 + add dword ptr [r11 + OFFSETOF__Thread__m_dwLockCount], 1 + +ifdef MON_DEBUG +ifdef TRACK_SYNC + mov rcx, [rsp + MON_ENTER_STACK_SIZE + 8h] ; return address + ; void EnterSyncHelper(UINT_PTR caller, AwareLock* lock) + call EnterSyncHelper +endif +endif + + ; Done, leave and set pbLockTaken if we have it + jmp LockTaken + + ; It's possible to get here with waiters by no lock held, but in this + ; case a signal is about to be fired which will wake up the waiter. So + ; for fairness sake we should wait too. + ; Check first for recur11ve lock attempts on the same thread. + HaveWaiters: + ; Is mutex already owned by current thread? + cmp [rdx + OFFSETOF__AwareLock__m_HoldingThread], r11 + jne PrepareToWait + + ; Yes, bump our use count. + add dword ptr [rdx + OFFSETOF__AwareLock__m_Recursion], 1 + +ifdef MON_DEBUG +ifdef TRACK_SYNC + mov rcx, [rsp + MON_ENTER_STACK_SIZE + 8h] ; return address + ; void EnterSyncHelper(UINT_PTR caller, AwareLock* lock) + call EnterSyncHelper +endif +endif + ; Done, leave and set pbLockTaken if we have it + jmp LockTaken + + PrepareToWait: + ; If we are on a MP system we try spinning for a certain number of iterations + cmp dword ptr [g_SystemInfo + OFFSETOF__g_SystemInfo__dwNumberOfProcessors], 1 + jle HaveWaiters1 + + ; Exponential backoff: delay by approximately 2*r10 clock cycles + mov eax, r10d + delayLoop: + pause ; indicate to the CPU that we are spin waiting + sub eax, 1 + jnz delayLoop + + ; Next time, wait a factor longer + imul r10d, dword ptr [g_SpinConstants + OFFSETOF__g_SpinConstants__dwBackoffFactor] + + cmp r10d, dword ptr [g_SpinConstants + OFFSETOF__g_SpinConstants__dwMaximumDuration] + jle RetrySyncBlock + + HaveWaiters1: + mov rcx, rdx + mov rdx, rsi + add rsp, MON_ENTER_STACK_SIZE + pop rsi + ; void JITutil_MonContention(AwareLock* lock, BYTE* pbLockTaken) + jmp JITutil_MonContention + + RetryHelperSyncBlock: + jmp RetrySyncBlock + + FramedLockHelper: + mov rdx, rsi + add rsp, MON_ENTER_STACK_SIZE + pop rsi + ; void JITutil_MonEnterWorker(Object* obj, BYTE* pbLockTaken) + jmp JITutil_MonEnterWorker + + align 16 + ; This is sensitive to the potential that pbLockTaken is NULL + LockTaken: + test rsi, rsi + jz LockTaken_Exit + mov byte ptr [rsi], 1 + LockTaken_Exit: + add rsp, MON_ENTER_STACK_SIZE + pop rsi + ret +NESTED_END JIT_MonEnterWorker_Slow, _TEXT + +; This is a frameless helper for exiting a monitor on a object. +; The object is in ARGUMENT_REG1. This tries the normal case (no +; blocking or object allocation) in line and calls a framed helper +; for the other cases. +; +; void JIT_MonExitWorker_Slow(Object* obj, BYTE* pbLockTaken) +NESTED_ENTRY JIT_MonExitWorker_Slow, _TEXT + alloc_stack MON_EXIT_STACK_SIZE + + save_reg_postrsp rcx, MON_EXIT_STACK_SIZE + 8h + 0h + save_reg_postrsp rdx, MON_EXIT_STACK_SIZE + 8h + 8h + save_reg_postrsp r8, MON_EXIT_STACK_SIZE + 8h + 10h + save_reg_postrsp r9, MON_EXIT_STACK_SIZE + 8h + 18h + + END_PROLOGUE + + ; pbLockTaken is stored in r10 + mov r10, rdx + + ; if pbLockTaken is NULL then we got here without a state variable, avoid the + ; next comparison in that case as it will AV + test rdx, rdx + jz Null_pbLockTaken + + ; If the lock wasn't taken then we bail quickly without doing anything + cmp byte ptr [rdx], 0 + je LockNotTaken + + Null_pbLockTaken: + ; Check is the instance is null + test rcx, rcx + jz FramedLockHelper + + ; The Thread obj address is stored in r11 + CALL_GETTHREAD + mov r11, rax + + ; r8 will hold the syncblockindex address + lea r8, [rcx - OFFSETOF__ObjHeader__SyncBlkIndex] + + RetryThinLock: + ; Fetch the syncblock dword + mov eax, dword ptr [r8] + test eax, BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX + BIT_SBLK_SPIN_LOCK + jnz NeedMoreTests + + ; Ok, we have a "thin lock" layout - check whether the thread id matches + mov edx, eax + and edx, SBLK_MASK_LOCK_THREADID + cmp edx, dword ptr [r11 + OFFSETOF__Thread__m_ThreadId] + jne FramedLockHelper + + ; check the recursion level + test eax, SBLK_MASK_LOCK_RECLEVEL + jne DecRecursionLevel + + ; It's zero -- we're leaving the lock. + ; So try to put back a zero thread id. + ; edx and eax match in the thread id bits, and edx is zero else where, so the xor is sufficient + xor edx, eax + lock cmpxchg dword ptr [r8], edx + jnz RetryHelperThinLock + + ; Dec the dwLockCount on the thread + sub dword ptr [r11 + OFFSETOF__Thread__m_dwLockCount], 1 + + ; Done, leave and set pbLockTaken if we have it + jmp LockReleased + + DecRecursionLevel: + lea edx, [eax - SBLK_LOCK_RECLEVEL_INC] + lock cmpxchg dword ptr [r8], edx + jnz RetryHelperThinLock + + ; We're done, leave and set pbLockTaken if we have it + jmp LockReleased + + NeedMoreTests: + ; Forward all special cases to the slow helper + test eax, BIT_SBLK_IS_HASHCODE + BIT_SBLK_SPIN_LOCK + jnz FramedLockHelper + + ; Get the sync block index and use it to compute the sync block pointer + mov rdx, qword ptr [g_pSyncTable] + and eax, MASK_SYNCBLOCKINDEX + shl eax, 4 + mov rdx, [rdx + rax + OFFSETOF__SyncTableEntry__m_SyncBlock] + + ; Was there a sync block? + test rdx, rdx + jz FramedLockHelper + + ; Get a pointer to the lock object. + lea rdx, [rdx + OFFSETOF__SyncBlock__m_Monitor] + + ; Check if the lock is held. + cmp qword ptr [rdx + OFFSETOF__AwareLock__m_HoldingThread], r11 + jne FramedLockHelper + +ifdef MON_DEBUG +ifdef TRACK_SYNC + mov [rsp + 28h], rcx + mov [rsp + 30h], rdx + mov [rsp + 38h], r10 + mov [rsp + 40h], r11 + + mov rcx, [rsp + MON_EXIT_STACK_SIZE ] ; return address + ; void LeaveSyncHelper(UINT_PTR caller, AwareLock* lock) + call LeaveSyncHelper + + mov rcx, [rsp + 28h] + mov rdx, [rsp + 30h] + mov r10, [rsp + 38h] + mov r11, [rsp + 40h] +endif +endif + + ; Reduce our recursion count + sub dword ptr [rdx + OFFSETOF__AwareLock__m_Recursion], 1 + jz LastRecursion + + ; Done, leave and set pbLockTaken if we have it + jmp LockReleased + + RetryHelperThinLock: + jmp RetryThinLock + + FramedLockHelper: + mov rdx, r10 + add rsp, MON_EXIT_STACK_SIZE + ; void JITutil_MonExitWorker(Object* obj, BYTE* pbLockTaken) + jmp JITutil_MonExitWorker + + LastRecursion: +ifdef MON_DEBUG +ifdef TRACK_SYNC + mov rax, [rdx + OFFSETOF__AwareLock__m_HoldingThread] +endif +endif + + sub dword ptr [r11 + OFFSETOF__Thread__m_dwLockCount], 1 + mov qword ptr [rdx + OFFSETOF__AwareLock__m_HoldingThread], 0 + + Retry: + mov eax, dword ptr [rdx + OFFSETOF__AwareLock__m_MonitorHeld] + lea r9d, [eax - 1] + lock cmpxchg dword ptr [rdx + OFFSETOF__AwareLock__m_MonitorHeld], r9d + jne RetryHelper + + test eax, STATE_CHECK + jne MustSignal + + ; Done, leave and set pbLockTaken if we have it + jmp LockReleased + + MustSignal: + mov rcx, rdx + mov rdx, r10 + add rsp, MON_EXIT_STACK_SIZE + ; void JITutil_MonSignal(AwareLock* lock, BYTE* pbLockTaken) + jmp JITutil_MonSignal + + RetryHelper: + jmp Retry + + LockNotTaken: + add rsp, MON_EXIT_STACK_SIZE + ret + + align 16 + ; This is sensitive to the potential that pbLockTaken is null + LockReleased: + test r10, r10 + jz LockReleased_Exit + mov byte ptr [r10], 0 + LockReleased_Exit: + add rsp, MON_EXIT_STACK_SIZE + ret +NESTED_END JIT_MonExitWorker_Slow, _TEXT + +; This is a frameless helper for trying to enter a monitor on a object. +; The object is in ARGUMENT_REG1 and a timeout in ARGUMENT_REG2. This tries the +; normal case (no object allocation) in line and calls a framed helper for the +; other cases. +; +; void JIT_MonTryEnter_Slow(Object* obj, INT32 timeOut, BYTE* pbLockTaken) +NESTED_ENTRY JIT_MonTryEnter_Slow, _TEXT + push_nonvol_reg rsi + + alloc_stack MON_ENTER_STACK_SIZE + + save_reg_postrsp rcx, MON_ENTER_STACK_SIZE + 10h + 0h + save_reg_postrsp rdx, MON_ENTER_STACK_SIZE + 10h + 8h + save_reg_postrsp r8, MON_ENTER_STACK_SIZE + 10h + 10h + save_reg_postrsp r9, MON_ENTER_STACK_SIZE + 10h + 18h + + END_PROLOGUE + + mov rsi, rdx + + ; Check if the instance is NULL + test rcx, rcx + jz FramedLockHelper + + ; Check if the timeout looks valid + cmp rdx, -1 + jl FramedLockHelper + + ; We store the thread object in r11 + CALL_GETTHREAD + mov r11, rax + + ; Initialize delay value for retry with exponential backoff + mov r10d, dword ptr [g_SpinConstants + OFFSETOF__g_SpinConstants__dwInitialDuration] + + ; Check if we can abort here + mov eax, dword ptr [r11 + OFFSETOF__Thread__m_State] + and eax, THREAD_CATCHATSAFEPOINT_BITS + ; Go through the slow code path to initiate THreadAbort + jnz FramedLockHelper + + ; r9 will hold the syncblockindex address + lea r9, [rcx - OFFSETOF__ObjHeader__SyncBlkIndex] + + RetryThinLock: + ; Fetch the syncblock dword + mov eax, dword ptr [r9] + + ; Check whether we have the "thin lock" layout, the lock is free and the spin lock bit is not set + test eax, BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX + BIT_SBLK_SPIN_LOCK + SBLK_MASK_LOCK_THREADID + SBLK_MASK_LOCK_RECLEVEL + jne NeedMoreTests + + ; Everything is fine - get the thread id to store in the lock + mov edx, dword ptr [r11 + OFFSETOF__Thread__m_ThreadId] + + ; If the thread id is too large, we need a syncblock for sure + cmp edx, SBLK_MASK_LOCK_THREADID + ja FramedLockHelper + + ; We want to store a new value with the current thread id set in the low 10 bits + or edx, eax + lock cmpxchg dword ptr [r9], edx + jnz RetryHelperThinLock + + ; Got the lock, everything is fine + add dword ptr [r11 + OFFSETOF__Thread__m_dwLockCount], 1 + ; Return TRUE + mov byte ptr [r8], 1 + add rsp, MON_ENTER_STACK_SIZE + pop rsi + ret + + NeedMoreTests: + ; OK, not the simple case, find out which case it is + test eax, BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX + jnz HaveHashOrSyncBlockIndex + + ; The header is transitioning or the lock + test eax, BIT_SBLK_SPIN_LOCK + jnz RetryHelperThinLock + + ; Here we know we have the "thin lock" layout, but the lock is not free. + ; It could still be the recursion case, compare the thread id to check + mov edx, eax + and edx, SBLK_MASK_LOCK_THREADID + cmp edx, dword ptr [r11 + OFFSETOF__Thread__m_ThreadId] + jne PrepareToWaitThinLock + + ; Ok, the thread id matches, it's the recursion case. + ; Dump up the recursion level and check for overflow + lea edx, [eax + SBLK_LOCK_RECLEVEL_INC] + test edx, SBLK_MASK_LOCK_RECLEVEL + jz FramedLockHelper + + ; Try to put the new recursion level back. If the header was changed in the meantime + ; we need a full retry, because the layout could have changed + lock cmpxchg dword ptr [r9], edx + jnz RetryHelperThinLock + + ; Everything went fine and we're done, return TRUE + mov byte ptr [r8], 1 + add rsp, MON_ENTER_STACK_SIZE + pop rsi + ret + + PrepareToWaitThinLock: + ; Return failure if timeout is zero + test rsi, rsi + jz TimeoutZero + + ; If we are on an MP system, we try spinning for a certain number of iterations + cmp dword ptr [g_SystemInfo + OFFSETOF__g_SystemInfo__dwNumberOfProcessors], 1 + jle FramedLockHelper + + ; Exponential backoff; delay by approximately 2*r10d clock cycles + mov eax, r10d + DelayLoopThinLock: + pause ; indicate to the CPU that we are spin waiting + sub eax, 1 + jnz DelayLoopThinLock + + ; Next time, wait a factor longer + imul r10d, dword ptr [g_SpinConstants + OFFSETOF__g_SpinConstants__dwBackoffFactor] + + cmp r10d, dword ptr [g_SpinConstants + OFFSETOF__g_SpinConstants__dwMaximumDuration] + jle RetryHelperThinLock + + jmp FramedLockHelper + + RetryHelperThinLock: + jmp RetryThinLock + + HaveHashOrSyncBlockIndex: + ; If we have a hash code already, we need to create a sync block + test eax, BIT_SBLK_IS_HASHCODE + jnz FramedLockHelper + + ; OK, we have a sync block index, just and out the top bits and grab the synblock index + and eax, MASK_SYNCBLOCKINDEX + + ; Get the sync block pointer + mov rdx, qword ptr [g_pSyncTable] + shl eax, 4 + mov rdx, [rdx + rax + OFFSETOF__SyncTableEntry__m_SyncBlock] + + ; Check if the sync block has been allocated + test rdx, rdx + jz FramedLockHelper + + ; Get a pointer to the lock object + lea rdx, [rdx + OFFSETOF__SyncBlock__m_Monitor] + + RetrySyncBlock: + ; Attempt to acuire the lock + mov eax, dword ptr [rdx + OFFSETOF__AwareLock__m_MonitorHeld] + test eax, eax + jne HaveWaiters + + ; Common case, lock isn't held and there are no waiters. Attempt to + ; gain ownership ourselves + xor ecx, ecx + inc ecx + lock cmpxchg dword ptr [rdx + OFFSETOF__AwareLock__m_MonitorHeld], ecx + jnz RetryHelperSyncBlock + + ; Success. Save the thread object in the lock and increment the use count + mov qword ptr [rdx + OFFSETOF__AwareLock__m_HoldingThread], r11 + add dword ptr [rdx + OFFSETOF__AwareLock__m_Recursion], 1 + add dword ptr [r11 + OFFSETOF__Thread__m_dwLockCount], 1 + +ifdef MON_DEBUG +ifdef TRACK_SYNC + mov rcx, [rsp + MON_ENTER_STACK_SIZE + 8h] ; return address + ; void EnterSyncHelper(UINT_PTR caller, AwareLock* lock) + call EnterSyncHelper +endif +endif + + ; Return TRUE + mov byte ptr [r8], 1 + add rsp, MON_ENTER_STACK_SIZE + pop rsi + ret + + ; It's possible to get here with waiters by no lock held, but in this + ; case a signal is about to be fired which will wake up the waiter. So + ; for fairness sake we should wait too. + ; Check first for recur11ve lock attempts on the same thread. + HaveWaiters: + ; Is mutex already owned by current thread? + cmp [rdx + OFFSETOF__AwareLock__m_HoldingThread], r11 + jne PrepareToWait + + ; Yes, bump our use count. + add dword ptr [rdx + OFFSETOF__AwareLock__m_Recursion], 1 + +ifdef MON_DEBUG +ifdef TRACK_SYNC + mov rcx, [rsp + MON_ENTER_STACK_SIZE + 8h] ; return address + ; void EnterSyncHelper(UINT_PTR caller, AwareLock* lock) + call EnterSyncHelper +endif +endif + + ; Return TRUE + mov byte ptr [r8], 1 + add rsp, MON_ENTER_STACK_SIZE + pop rsi + ret + + PrepareToWait: + ; Return failure if timeout is zero + test rsi, rsi + jz TimeoutZero + + ; If we are on an MP system, we try spinning for a certain number of iterations + cmp dword ptr [g_SystemInfo + OFFSETOF__g_SystemInfo__dwNumberOfProcessors], 1 + jle Block + + ; Exponential backoff; delay by approximately 2*r10d clock cycles + mov eax, r10d + DelayLoop: + pause ; indicate to the CPU that we are spin waiting + sub eax, 1 + jnz DelayLoop + + ; Next time, wait a factor longer + imul r10d, dword ptr [g_SpinConstants + OFFSETOF__g_SpinConstants__dwBackoffFactor] + + cmp r10d, dword ptr [g_SpinConstants + OFFSETOF__g_SpinConstants__dwMaximumDuration] + jle RetrySyncBlock + + jmp Block + + TimeoutZero: + ; Return FALSE + mov byte ptr [r8], 0 + add rsp, MON_ENTER_STACK_SIZE + pop rsi + ret + + RetryHelperSyncBlock: + jmp RetrySyncBlock + + Block: + ; In the Block case we've trashed RCX, restore it + mov rcx, [rsp + MON_ENTER_STACK_SIZE + 10h] + FramedLockHelper: + mov rdx, rsi + add rsp, MON_ENTER_STACK_SIZE + pop rsi + ; void JITutil_MonTryEnter(Object* obj, UINT32 timeout, BYTE* pbLockTaken) + jmp JITutil_MonTryEnter + +NESTED_END JIT_MonTryEnter_Slow, _TEXT + +MON_ENTER_STATIC_RETURN_SUCCESS macro + ; pbLockTaken is never null for static helpers + mov byte ptr [rdx], 1 + add rsp, MIN_SIZE + ret + + endm + +MON_EXIT_STATIC_RETURN_SUCCESS macro + ; pbLockTaken is never null for static helpers + mov byte ptr [rdx], 0 + add rsp, MIN_SIZE + ret + + endm + + +; This is a frameless helper for entering a static monitor on a class. +; The methoddesc is in ARGUMENT_REG1. This tries the normal case (no +; blocking or object allocation) in line and calls a framed helper +; for the other cases. +; +; void JIT_MonEnterStatic_Slow(AwareLock *lock, BYTE *pbLockTaken) +NESTED_ENTRY JIT_MonEnterStatic_Slow, _TEXT + alloc_stack MIN_SIZE + END_PROLOGUE + + ; Attempt to acquire the lock + Retry: + mov eax, dword ptr [rcx + OFFSETOF__AwareLock__m_MonitorHeld] + test eax, eax + jne HaveWaiters + + ; Common case; lock isn't held and there are no waiters. Attempt to + ; gain ownership by ourselves. + mov r10d, 1 + lock cmpxchg dword ptr [rcx + OFFSETOF__AwareLock__m_MonitorHeld], r10d + jnz RetryHelper + + ; Success. Save the thread object in the lock and increment the use count. + CALL_GETTHREAD + + mov qword ptr [rcx + OFFSETOF__AwareLock__m_HoldingThread], rax + add dword ptr [rcx + OFFSETOF__AwareLock__m_Recursion], 1 + add dword ptr [rax + OFFSETOF__Thread__m_dwLockCount], 1 + +ifdef MON_DEBUG +ifdef TRACK_SYNC + add rsp, MIN_SIZE + mov rdx, rcx + mov rcx, [rsp] + ; void EnterSyncHelper(UINT_PTR caller, AwareLock* lock) + jmp EnterSyncHelper +endif +endif + MON_ENTER_STATIC_RETURN_SUCCESS + + ; It's possible to get here with waiters by with no lock held, in this + ; case a signal is about to be fired which will wake up a waiter. So + ; for fairness sake we should wait too. + ; Check first for recursive lock attempts on the same thread. + HaveWaiters: + CALL_GETTHREAD + + ; Is mutex alread owned by current thread? + cmp [rcx + OFFSETOF__AwareLock__m_HoldingThread], rax + jne PrepareToWait + + ; Yes, bump our use count. + add dword ptr [rcx + OFFSETOF__AwareLock__m_Recursion], 1 +ifdef MON_DEBUG +ifdef TRACK_SYNC + mov rdx, rcx + mov rcx, [rsp] + ; void EnterSyncHelper(UINT_PTR caller, AwareLock* lock) + add rsp, MIN_SIZE + jmp EnterSyncHelper +endif +endif + MON_ENTER_STATIC_RETURN_SUCCESS + + PrepareToWait: + add rsp, MIN_SIZE + ; void JITutil_MonContention(AwareLock* obj, BYTE* pbLockTaken) + jmp JITutil_MonContention + + RetryHelper: + jmp Retry +NESTED_END JIT_MonEnterStatic_Slow, _TEXT + +; A frameless helper for exiting a static monitor on a class. +; The methoddesc is in ARGUMENT_REG1. This tries the normal case (no +; blocking or object allocation) in line and calls a framed helper +; for the other cases. +; +; void JIT_MonExitStatic_Slow(AwareLock *lock, BYTE *pbLockTaken) +NESTED_ENTRY JIT_MonExitStatic_Slow, _TEXT + alloc_stack MIN_SIZE + END_PROLOGUE + +ifdef MON_DEBUG +ifdef TRACK_SYNC + push rsi + push rdi + mov rsi, rcx + mov rdi, rdx + mov rdx, [rsp + 8] + call LeaveSyncHelper + mov rcx, rsi + mov rdx, rdi + pop rdi + pop rsi +endif +endif + + ; Check if lock is held + CALL_GETTHREAD + + cmp [rcx + OFFSETOF__AwareLock__m_HoldingThread], rax + jne LockError + + ; Reduce our recursion count + sub dword ptr [rcx + OFFSETOF__AwareLock__m_Recursion], 1 + jz LastRecursion + + MON_EXIT_STATIC_RETURN_SUCCESS + + ; This is the last count we held on this lock, so release the lock + LastRecursion: + ; Thead* is in rax + sub dword ptr [rax + OFFSETOF__Thread__m_dwLockCount], 1 + mov qword ptr [rcx + OFFSETOF__AwareLock__m_HoldingThread], 0 + + Retry: + mov eax, dword ptr [rcx + OFFSETOF__AwareLock__m_MonitorHeld] + lea r10d, [eax - 1] + lock cmpxchg dword ptr [rcx + OFFSETOF__AwareLock__m_MonitorHeld], r10d + jne RetryHelper + test eax, STATE_CHECK + jne MustSignal + + MON_EXIT_STATIC_RETURN_SUCCESS + + MustSignal: + add rsp, MIN_SIZE + ; void JITutil_MonSignal(AwareLock* lock, BYTE* pbLockTaken) + jmp JITutil_MonSignal + + RetryHelper: + jmp Retry + + LockError: + mov rcx, CORINFO_SynchronizationLockException_ASM + add rsp, MIN_SIZE + ; void JIT_InternalThrow(unsigned exceptNum) + jmp JIT_InternalThrow +NESTED_END JIT_MonExitStatic_Slow, _TEXT + + +ifdef _DEBUG + +extern Object__DEBUG_SetAppDomain:proc + +; +; IN: rax: new object needing the AppDomain ID set.. +; OUT: rax, returns original value at entry +; +; all integer register state is preserved +; +DEBUG_TrialAllocSetAppDomain_STACK_SIZE equ MIN_SIZE + 10h +NESTED_ENTRY DEBUG_TrialAllocSetAppDomain, _TEXT + push_vol_reg rax + push_vol_reg rcx + push_vol_reg rdx + push_vol_reg r8 + push_vol_reg r9 + push_vol_reg r10 + push_vol_reg r11 + push_nonvol_reg rbx + alloc_stack MIN_SIZE + END_PROLOGUE + + mov rbx, rax + + ; get the app domain ptr + CALL_GETAPPDOMAIN + + ; set the sync block app domain ID + mov rcx, rbx + mov rdx, rax + call Object__DEBUG_SetAppDomain + + ; epilog + add rsp, MIN_SIZE + pop rbx + pop r11 + pop r10 + pop r9 + pop r8 + pop rdx + pop rcx + pop rax + ret +NESTED_END DEBUG_TrialAllocSetAppDomain, _TEXT + +NESTED_ENTRY DEBUG_TrialAllocSetAppDomain_NoScratchArea, _TEXT + + push_nonvol_reg rbp + set_frame rbp, 0 + END_PROLOGUE + + sub rsp, 20h + and rsp, -16 + + call DEBUG_TrialAllocSetAppDomain + + lea rsp, [rbp+0] + pop rbp + ret +NESTED_END DEBUG_TrialAllocSetAppDomain_NoScratchArea, _TEXT + +endif + + + end + diff --git a/src/vm/amd64/PInvokeStubs.asm b/src/vm/amd64/PInvokeStubs.asm new file mode 100644 index 0000000000..43f1a4752f --- /dev/null +++ b/src/vm/amd64/PInvokeStubs.asm @@ -0,0 +1,282 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; ==++== +; + +; +; ==--== + +include AsmMacros.inc +include AsmConstants.inc + +extern GenericPInvokeCalliStubWorker:proc +extern VarargPInvokeStubWorker:proc + +ifdef FEATURE_INCLUDE_ALL_INTERFACES +PInvokeStubForHostWorker equ ?PInvokeStubForHostWorker@@YAXKPEAX0@Z +extern PInvokeStubForHostWorker:proc + +PInvokeStubForHost_CALLEE_SCRATCH_SIZE = 20h + +PInvokeStubForHost_STACK_FRAME_SIZE = PInvokeStubForHost_CALLEE_SCRATCH_SIZE + +; 4 FP parameter registers +PInvokeStubForHost_XMM_SAVE_OFFSET = PInvokeStubForHost_STACK_FRAME_SIZE +PInvokeStubForHost_STACK_FRAME_SIZE = PInvokeStubForHost_STACK_FRAME_SIZE + 40h + +; Ensure that the new rsp will be 16-byte aligned. +if ((PInvokeStubForHost_STACK_FRAME_SIZE + 8) MOD 16) ne 0 +PInvokeStubForHost_STACK_FRAME_SIZE = PInvokeStubForHost_STACK_FRAME_SIZE + 8 +endif + +; Return address is immediately above the local variables. +PInvokeStubForHost_RETURN_ADDRESS_OFFSET = PInvokeStubForHost_STACK_FRAME_SIZE +PInvokeStubForHost_PARAM_REGISTERS_OFFSET = PInvokeStubForHost_RETURN_ADDRESS_OFFSET + 8 + +NESTED_ENTRY PInvokeStubForHost, _TEXT + alloc_stack PInvokeStubForHost_STACK_FRAME_SIZE + END_PROLOGUE + + ; spill args + mov [rsp + PInvokeStubForHost_PARAM_REGISTERS_OFFSET + 0h], rcx + mov [rsp + PInvokeStubForHost_PARAM_REGISTERS_OFFSET + 8h], rdx + mov [rsp + PInvokeStubForHost_PARAM_REGISTERS_OFFSET + 10h], r8 + mov [rsp + PInvokeStubForHost_PARAM_REGISTERS_OFFSET + 18h], r9 + movdqa [rsp + PInvokeStubForHost_XMM_SAVE_OFFSET + 0h], xmm0 + movdqa [rsp + PInvokeStubForHost_XMM_SAVE_OFFSET + 10h], xmm1 + movdqa [rsp + PInvokeStubForHost_XMM_SAVE_OFFSET + 20h], xmm2 + movdqa [rsp + PInvokeStubForHost_XMM_SAVE_OFFSET + 30h], xmm3 + + ; PInvokeStubForHostWorker(#stack args, stack frame, this) + mov r8, rcx + mov rcx, r11 + mov rdx, rsp + call PInvokeStubForHostWorker + + ; unspill return value + mov rax, [rsp + PInvokeStubForHost_XMM_SAVE_OFFSET + 0h] + movdqa xmm0, [rsp + PInvokeStubForHost_XMM_SAVE_OFFSET + 10h] + + add rsp, PInvokeStubForHost_STACK_FRAME_SIZE + ret +NESTED_END PInvokeStubForHost, _TEXT + + +PInvokeStubForHostInner_STACK_FRAME_SIZE = 0 + +; integer registers saved in prologue +PInvokeStubForHostInner_NUM_REG_PUSHES = 2 +PInvokeStubForHostInner_STACK_FRAME_SIZE = PInvokeStubForHostInner_STACK_FRAME_SIZE + PInvokeStubForHostInner_NUM_REG_PUSHES*8 + +; Ensure that the new rsp will be 16-byte aligned. +if ((PInvokeStubForHostInner_STACK_FRAME_SIZE + 8) MOD 16) ne 0 +PInvokeStubForHostInner_STACK_FRAME_SIZE = PInvokeStubForHostInner_STACK_FRAME_SIZE + 8 +endif + +; Return address is immediately above the local variables. +PInvokeStubForHostInner_RETURN_ADDRESS_OFFSET = PInvokeStubForHostInner_STACK_FRAME_SIZE +PInvokeStubForHostInner_PARAM_REGISTERS_OFFSET = PInvokeStubForHostInner_RETURN_ADDRESS_OFFSET + 8 + +PInvokeStubForHostInner_FRAME_OFFSET = PInvokeStubForHost_CALLEE_SCRATCH_SIZE + +; RCX - #stack args +; RDX - PInvokeStubForHost's stack frame +; R8 - target address +NESTED_ENTRY PInvokeStubForHostInner, _TEXT + + push_nonvol_reg rbp + push_nonvol_reg r12 + alloc_stack PInvokeStubForHostInner_FRAME_OFFSET + PInvokeStubForHostInner_STACK_FRAME_SIZE - PInvokeStubForHostInner_NUM_REG_PUSHES*8 + set_frame rbp, PInvokeStubForHostInner_FRAME_OFFSET + END_PROLOGUE + + mov r10, r8 + mov r12, rdx + + test rcx, rcx + jnz HandleStackArgs + + ; + ; Allocate space for scratch area if there are no stack args. + ; + sub rsp, PInvokeStubForHost_CALLEE_SCRATCH_SIZE + +DoneStackArgs: + ; unspill args + mov rcx, [r12 + PInvokeStubForHost_PARAM_REGISTERS_OFFSET + 0h] + mov rdx, [r12 + PInvokeStubForHost_PARAM_REGISTERS_OFFSET + 8h] + mov r8, [r12 + PInvokeStubForHost_PARAM_REGISTERS_OFFSET + 10h] + mov r9, [r12 + PInvokeStubForHost_PARAM_REGISTERS_OFFSET + 18h] + movdqa xmm0, [r12 + PInvokeStubForHost_XMM_SAVE_OFFSET + 0h] + movdqa xmm1, [r12 + PInvokeStubForHost_XMM_SAVE_OFFSET + 10h] + movdqa xmm2, [r12 + PInvokeStubForHost_XMM_SAVE_OFFSET + 20h] + movdqa xmm3, [r12 + PInvokeStubForHost_XMM_SAVE_OFFSET + 30h] + + call r10 + + ; spill return value + mov [r12 + PInvokeStubForHost_XMM_SAVE_OFFSET + 0h], rax + movdqa [r12 + PInvokeStubForHost_XMM_SAVE_OFFSET + 10h], xmm0 + + ; epilogue + lea rsp, [rbp + PInvokeStubForHostInner_RETURN_ADDRESS_OFFSET - PInvokeStubForHostInner_NUM_REG_PUSHES*8] + pop r12 + pop rbp + ret + +; INPUTS: +; RDX - number of stack bytes +; R12 - the outer method's frame pointer +; RSP - +; RBP - +; +HandleStackArgs: + ; + ; Allocate space for stack parameters + scratch area. + ; + sub rsp, rcx + and rsp, -16 + sub rsp, PInvokeStubForHost_CALLEE_SCRATCH_SIZE + + ; + ; Copy stack parameters + ; + shr rcx, 3 ; setup count + + mov r8, rdi + mov r9, rsi + + lea rdi, [rsp + PInvokeStubForHost_CALLEE_SCRATCH_SIZE] ; rdi -> above callee scratch area + lea rsi, [r12 + PInvokeStubForHost_PARAM_REGISTERS_OFFSET + PInvokeStubForHost_CALLEE_SCRATCH_SIZE] + rep movsq + + mov rsi, r9 ; restore rsi + mov rdi, r8 ; restore rdi + jmp DoneStackArgs +NESTED_END PInvokeStubForHostInner, _TEXT +endif ; FEATURE_INCLUDE_ALL_INTERFACES + +; +; in: +; PINVOKE_CALLI_TARGET_REGISTER (r10) = unmanaged target +; PINVOKE_CALLI_SIGTOKEN_REGNUM (r11) = sig token +; +; out: +; METHODDESC_REGISTER (r10) = unmanaged target +; +LEAF_ENTRY GenericPInvokeCalliHelper, _TEXT + + ; + ; check for existing IL stub + ; + mov rax, [PINVOKE_CALLI_SIGTOKEN_REGISTER + OFFSETOF__VASigCookie__pNDirectILStub] + test rax, rax + jz GenericPInvokeCalliGenILStub + + ; + ; We need to distinguish between a MethodDesc* and an unmanaged target in PInvokeStubForHost(). + ; The way we do this is to shift the managed target to the left by one bit and then set the + ; least significant bit to 1. This works because MethodDesc* are always 8-byte aligned. + ; + shl PINVOKE_CALLI_TARGET_REGISTER, 1 + or PINVOKE_CALLI_TARGET_REGISTER, 1 + + ; + ; jump to existing IL stub + ; + jmp rax + +LEAF_END GenericPInvokeCalliHelper, _TEXT + +NESTED_ENTRY GenericPInvokeCalliGenILStub, _TEXT + + PROLOG_WITH_TRANSITION_BLOCK + + ; + ; save target + ; + mov r12, METHODDESC_REGISTER + mov r13, PINVOKE_CALLI_SIGTOKEN_REGISTER + + ; + ; GenericPInvokeCalliStubWorker(TransitionBlock * pTransitionBlock, VASigCookie * pVASigCookie, PCODE pUnmanagedTarget) + ; + lea rcx, [rsp + __PWTB_TransitionBlock] ; pTransitionBlock* + mov rdx, PINVOKE_CALLI_SIGTOKEN_REGISTER ; pVASigCookie + mov r8, METHODDESC_REGISTER ; pUnmanagedTarget + call GenericPInvokeCalliStubWorker + + ; + ; restore target + ; + mov METHODDESC_REGISTER, r12 + mov PINVOKE_CALLI_SIGTOKEN_REGISTER, r13 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + jmp GenericPInvokeCalliHelper + +NESTED_END GenericPInvokeCalliGenILStub, _TEXT + +LEAF_ENTRY VarargPInvokeStub, _TEXT + mov PINVOKE_CALLI_SIGTOKEN_REGISTER, rcx + jmp VarargPInvokeStubHelper +LEAF_END VarargPInvokeStub, _TEXT + +LEAF_ENTRY VarargPInvokeStub_RetBuffArg, _TEXT + mov PINVOKE_CALLI_SIGTOKEN_REGISTER, rdx + jmp VarargPInvokeStubHelper +LEAF_END VarargPInvokeStub_RetBuffArg, _TEXT + +LEAF_ENTRY VarargPInvokeStubHelper, _TEXT + ; + ; check for existing IL stub + ; + mov rax, [PINVOKE_CALLI_SIGTOKEN_REGISTER + OFFSETOF__VASigCookie__pNDirectILStub] + test rax, rax + jz VarargPInvokeGenILStub + + ; + ; jump to existing IL stub + ; + jmp rax + +LEAF_END VarargPInvokeStubHelper, _TEXT + +; +; IN: METHODDESC_REGISTER (R10) stub secret param +; PINVOKE_CALLI_SIGTOKEN_REGISTER (R11) VASigCookie* +; +; ASSUMES: we already checked for an existing stub to use +; +NESTED_ENTRY VarargPInvokeGenILStub, _TEXT + + PROLOG_WITH_TRANSITION_BLOCK + + ; + ; save target + ; + mov r12, METHODDESC_REGISTER + mov r13, PINVOKE_CALLI_SIGTOKEN_REGISTER + + ; + ; VarargPInvokeStubWorker(TransitionBlock * pTransitionBlock, VASigCookie *pVASigCookie, MethodDesc *pMD) + ; + lea rcx, [rsp + __PWTB_TransitionBlock] ; pTransitionBlock* + mov rdx, PINVOKE_CALLI_SIGTOKEN_REGISTER ; pVASigCookie + mov r8, METHODDESC_REGISTER ; pMD + call VarargPInvokeStubWorker + + ; + ; restore target + ; + mov METHODDESC_REGISTER, r12 + mov PINVOKE_CALLI_SIGTOKEN_REGISTER, r13 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + jmp VarargPInvokeStubHelper + +NESTED_END VarargPInvokeGenILStub, _TEXT + + end diff --git a/src/vm/amd64/RedirectedHandledJITCase.asm b/src/vm/amd64/RedirectedHandledJITCase.asm new file mode 100644 index 0000000000..a6d3496357 --- /dev/null +++ b/src/vm/amd64/RedirectedHandledJITCase.asm @@ -0,0 +1,239 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; ==++== +; + +; +; ==--== + +include AsmMacros.inc +include asmconstants.inc + +Thread__GetAbortContext equ ?GetAbortContext@Thread@@QEAAPEAU_CONTEXT@@XZ + +extern FixContextHandler:proc +extern LinkFrameAndThrow:proc +extern GetCurrentSavedRedirectContext:proc +extern Thread__GetAbortContext:proc +extern HijackHandler:proc +extern ThrowControlForThread:proc +extern FixRedirectContextHandler:proc + +; +; WARNING!! These functions immediately ruin thread unwindability. This is +; WARNING!! OK as long as there is a mechanism for saving the thread context +; WARNING!! prior to running these functions as well as a mechanism for +; WARNING!! restoring the context prior to any stackwalk. This means that +; WARNING!! we need to ensure that no GC can occur while the stack is +; WARNING!! unwalkable. This further means that we cannot allow any exception +; WARNING!! to occure when the stack is unwalkable +; + + +; If you edit this macro, make sure you update GetCONTEXTFromRedirectedStubStackFrame. +; This function is used by both the personality routine and the debugger to retrieve the original CONTEXT. +GenerateRedirectedHandledJITCaseStub macro reason + +extern ?RedirectedHandledJITCaseFor&reason&@Thread@@CAXXZ:proc + +NESTED_ENTRY RedirectedHandledJITCaseFor&reason&_Stub, _TEXT, FixRedirectContextHandler + + ; + ; To aid debugging, we'll fake a call to this function. Allocate an + ; extra stack slot that is hidden from the unwind info, where we can + ; stuff the "return address". If we wanted to preserve full + ; unwindability, we would need to copy all preserved registers. + ; Ordinarily, rbp is used for the frame pointer, so since we're only + ; interested is debugability, we'll just handle that common case. + ; + + push rax ; where to stuff the fake return address + push_nonvol_reg rbp ; save interrupted rbp for stack walk + alloc_stack 28h ; CONTEXT*, callee scratch area + set_frame rbp, 0 + +.errnz REDIRECTSTUB_ESTABLISHER_OFFSET_RBP, REDIRECTSTUB_ESTABLISHER_OFFSET_RBP has changed - update asm stubs + + END_PROLOGUE + + ; + ; Align rsp. rsp must misaligned at entry to any C function. + ; + and rsp, -16 + + ; + ; Save a copy of the redirect CONTEXT* in case an exception occurs. + ; The personality routine will use this to restore unwindability for + ; the exception dispatcher. + ; + call GetCurrentSavedRedirectContext + + mov [rbp+20h], rax +.errnz REDIRECTSTUB_RBP_OFFSET_CONTEXT - 20h, REDIRECTSTUB_RBP_OFFSET_CONTEXT has changed - update asm stubs + + ; + ; Fetch the interrupted rip and save it as our return address. + ; + mov rax, [rax + OFFSETOF__CONTEXT__Rip] + mov [rbp+30h], rax + + ; + ; Call target, which will do whatever we needed to do in the context + ; of the target thread, and will RtlRestoreContext when it is done. + ; + call ?RedirectedHandledJITCaseFor&reason&@Thread@@CAXXZ + + int 3 ; target shouldn't return. + +; Put a label here to tell the debugger where the end of this function is. +PATCH_LABEL RedirectedHandledJITCaseFor&reason&_StubEnd + +NESTED_END RedirectedHandledJITCaseFor&reason&_Stub, _TEXT + + endm + + +GenerateRedirectedHandledJITCaseStub GCThreadControl +GenerateRedirectedHandledJITCaseStub DbgThreadControl +GenerateRedirectedHandledJITCaseStub UserSuspend +GenerateRedirectedHandledJITCaseStub YieldTask + +ifdef _DEBUG +ifdef HAVE_GCCOVER +GenerateRedirectedHandledJITCaseStub GCStress +endif +endif + + +; scratch area; padding; GSCookie +OFFSET_OF_FRAME = SIZEOF_MAX_OUTGOING_ARGUMENT_HOMES + 8 + SIZEOF_GSCookie + +; force evaluation to avoid "expression is too complex errors" +SIZEOF__FaultingExceptionFrame = SIZEOF__FaultingExceptionFrame + +GenerateRedirectedStubWithFrame macro STUB, FILTER, TARGET + +altentry STUB&_RspAligned + +NESTED_ENTRY STUB, _TEXT, FILTER + + ; + ; IN: rcx: original IP before redirect + ; + + mov rdx, rsp + + ; This push of the return address must not be recorded in the unwind + ; info. After this push, unwinding will work. + push rcx + + test rsp, 0fh + jnz STUB&_FixRsp + +STUB&_RspAligned: + + ; Any stack operations hereafter must be recorded in the unwind info, but + ; only nonvolatile register locations are needed. Anything else is only + ; a "sub rsp, 8" to the unwinder. + + ; m_ctx must be 16-byte aligned +.errnz (OFFSET_OF_FRAME + SIZEOF__FaultingExceptionFrame) MOD 16 + + alloc_stack OFFSET_OF_FRAME + SIZEOF__FaultingExceptionFrame + +.errnz THROWSTUB_ESTABLISHER_OFFSET_FaultingExceptionFrame - OFFSET_OF_FRAME, THROWSTUB_ESTABLISHER_OFFSET_FaultingExceptionFrame has changed - update asm stubs + + END_PROLOGUE + + lea rcx, [rsp + OFFSET_OF_FRAME] + + mov dword ptr [rcx], 0 ; Initialize vtbl (it is not strictly necessary) + mov dword ptr [rcx + OFFSETOF__FaultingExceptionFrame__m_fFilterExecuted], 0 ; Initialize BOOL for personality routine + + call TARGET + + ; Target should not return. + int 3 + +NESTED_END STUB, _TEXT + +; This function is used by the stub above to adjust the stack alignment. The +; stub can't conditionally push something on the stack because the unwind +; encodings have no way to express that. +; +; CONSIDER: we could move the frame pointer above the FaultingExceptionFrame, +; and detect the misalignment adjustment in +; GetFrameFromRedirectedStubStackFrame. This is probably less code and more +; straightforward. +LEAF_ENTRY STUB&_FixRsp, _TEXT + + call STUB&_RspAligned + + ; Target should not return. + int 3 + +LEAF_END STUB&_FixRsp, _TEXT + + endm + + +REDIRECT_FOR_THROW_CONTROL_FRAME_SIZE = SIZEOF_MAX_OUTGOING_ARGUMENT_HOMES + 8 + +NESTED_ENTRY RedirectForThrowControl2, _TEXT + + ; On entry + ; rcx -> FaultingExceptionFrame + ; rdx -> Original RSP + + alloc_stack REDIRECT_FOR_THROW_CONTROL_FRAME_SIZE + + save_reg_postrsp rcx, REDIRECT_FOR_THROW_CONTROL_FRAME_SIZE + 8h ; FaultingExceptionFrame + save_reg_postrsp rdx, REDIRECT_FOR_THROW_CONTROL_FRAME_SIZE + 10h ; Original RSP + + END_PROLOGUE + + ; Fetch rip from a CONTEXT, and store it as our return address. + CALL_GETTHREAD + + mov rcx, rax + call Thread__GetAbortContext + + mov rax, [rax + OFFSETOF__CONTEXT__Rip] + mov rdx, [rsp + REDIRECT_FOR_THROW_CONTROL_FRAME_SIZE + 10h] ; Original RSP + mov [rdx - 8], rax + + mov rcx, [rsp + REDIRECT_FOR_THROW_CONTROL_FRAME_SIZE + 8h] ; FaultingExceptionFrame + call ThrowControlForThread + + ; ThrowControlForThread doesn't return. + int 3 + +NESTED_END RedirectForThrowControl2, _TEXT + +GenerateRedirectedStubWithFrame RedirectForThrowControl, HijackHandler, RedirectForThrowControl2 + + +NAKED_THROW_HELPER_FRAME_SIZE = SIZEOF_MAX_OUTGOING_ARGUMENT_HOMES + 8 + +NESTED_ENTRY NakedThrowHelper2, _TEXT + + ; On entry + ; rcx -> FaultingExceptionFrame + + alloc_stack NAKED_THROW_HELPER_FRAME_SIZE + END_PROLOGUE + + call LinkFrameAndThrow + + ; LinkFrameAndThrow doesn't return. + int 3 + +NESTED_END NakedThrowHelper2, _TEXT + +GenerateRedirectedStubWithFrame NakedThrowHelper, FixContextHandler, NakedThrowHelper2 + + + end + diff --git a/src/vm/amd64/RemotingThunksAMD64.asm b/src/vm/amd64/RemotingThunksAMD64.asm new file mode 100644 index 0000000000..6d555e8beb --- /dev/null +++ b/src/vm/amd64/RemotingThunksAMD64.asm @@ -0,0 +1,303 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +include AsmMacros.inc +include AsmConstants.inc +ifdef FEATURE_REMOTING + +extern CallDescrWorkerUnwindFrameChainHandler:proc + +extern TransparentProxyStubWorker:proc + +; Stack frame layout: +; +; (stack parameters) +; ... +; r9 +; r8 +; rdx +; rcx <- TPSCC_PARAMS_OFFSET +; return address <- TPSCC_STACK_FRAME_SIZE +; r10 <- TPSCC_R10_OFFSET +; xmm3 +; xmm2 +; xmm1 +; xmm0 <- TPSCC_XMM_SAVE_OFFSET +; callee's r9 +; callee's r8 +; callee's rdx +; callee's rcx + +TPSCC_XMM_SAVE_OFFSET = 20h +TPSCC_R10_OFFSET = 60h +TPSCC_STACK_FRAME_SIZE = 68h +TPSCC_PARAMS_OFFSET = 70h + +TRANSPARENT_PROXY_STUB_PROLOGUE macro + alloc_stack TPSCC_STACK_FRAME_SIZE + + save_reg_postrsp r10, TPSCC_R10_OFFSET + + SAVE_ARGUMENT_REGISTERS TPSCC_PARAMS_OFFSET + SAVE_FLOAT_ARGUMENT_REGISTERS TPSCC_XMM_SAVE_OFFSET + + END_PROLOGUE + + endm + +NESTED_ENTRY TransparentProxyStub, _TEXT, CallDescrWorkerUnwindFrameChainHandler + + TRANSPARENT_PROXY_STUB_PROLOGUE + + ;; rcx: this + ;; [rsp]: slot number + + mov rax, [rcx + TransparentProxyObject___stub] + mov rcx, [rcx + TransparentProxyObject___stubData] + call rax + + RESTORE_ARGUMENT_REGISTERS TPSCC_PARAMS_OFFSET + RESTORE_FLOAT_ARGUMENT_REGISTERS TPSCC_XMM_SAVE_OFFSET + + mov r10, [rsp + TPSCC_R10_OFFSET] + + test rax, rax + jnz CrossContext + + mov r11, [rcx + TransparentProxyObject___pMT] + + ; Convert the slot number (r10) into the code address (in rax) + ; See MethodTable.h for details on vtable layout + shr r10, MethodTable_VtableSlotsPerChunkLog2 + mov rax, [r11 + r10*8 + METHODTABLE_OFFSET_VTABLE] + + mov r10, [rsp + TPSCC_R10_OFFSET] ; Reload the slot + and r10, MethodTable_VtableSlotsPerChunk-1 + mov rax, [rax + r10*8] + + add rsp, TPSCC_STACK_FRAME_SIZE + TAILJMP_RAX + +CrossContext: + add rsp, TPSCC_STACK_FRAME_SIZE + jmp TransparentProxyStub_CrossContext + +NESTED_END TransparentProxyStub, _TEXT + + +NESTED_ENTRY TransparentProxyStub_CrossContext, _TEXT + + PROLOG_WITH_TRANSITION_BLOCK 8 + + ; + ; Call TransparentProxyStubWorker. + ; + lea rcx, [rsp + __PWTB_TransitionBlock] ; pTransitionBlock + mov rdx, r10 ; MethodDesc * + call TransparentProxyStubWorker + + ; handle FP return values + + lea rcx, [rsp + __PWTB_FloatArgumentRegisters - 8] + cmp rax, 4 + jne @F + movss xmm0, real4 ptr [rcx] +@@: + cmp rax, 8 + jne @F + movsd xmm0, real8 ptr [rcx] +@@: + ; load return value + mov rax, [rcx] + + EPILOG_WITH_TRANSITION_BLOCK_RETURN + +NESTED_END TransparentProxyStub_CrossContext, _TEXT + +LEAF_ENTRY TransparentProxyStubPatch, _TEXT + ; make sure that the basic block is unique + test eax,12 +PATCH_LABEL TransparentProxyStubPatchLabel + ret +LEAF_END TransparentProxyStubPatch, _TEXT + +;+---------------------------------------------------------------------------- +; +; Method: CRemotingServices::CallFieldGetter private +; +; Synopsis: Calls the field getter function (Object::__FieldGetter) in +; managed code by setting up the stack and calling the target +; +;+---------------------------------------------------------------------------- +; extern "C" +;void __stdcall CRemotingServices__CallFieldGetter( MethodDesc *pMD, +; LPVOID pThis, +; LPVOID pFirst, +; LPVOID pSecond, +; LPVOID pThird +; ) +LEAF_ENTRY CRemotingServices__CallFieldGetter, _TEXT + +; +28 pThird +; +20 scratch area +; +18 scratch area +; +10 scratch area +; + 8 scratch area +; rsp return address + + mov METHODDESC_REGISTER, rcx + mov rcx, rdx + mov rdx, r8 + mov r8, r9 + mov r9, [rsp + 28h] + jmp TransparentProxyStub + +LEAF_END CRemotingServices__CallFieldGetter, _TEXT + + +;+---------------------------------------------------------------------------- +; +; Method: CRemotingServices::CallFieldSetter private +; +; Synopsis: Calls the field setter function (Object::__FieldSetter) in +; managed code by setting up the stack and calling the target +; +;+---------------------------------------------------------------------------- +; extern "C" +;void __stdcall CRemotingServices__CallFieldSetter( MethodDesc *pMD, +; LPVOID pThis, +; LPVOID pFirst, +; LPVOID pSecond, +; LPVOID pThird +; ) +LEAF_ENTRY CRemotingServices__CallFieldSetter, _TEXT + +; +28 pThird +; +20 scratch area +; +18 scratch area +; +10 scratch area +; + 8 scratch area +; rsp return address + + mov METHODDESC_REGISTER, rcx + mov rcx, rdx + mov rdx, r8 + mov r8, r9 + mov r9, [rsp + 28h] + jmp TransparentProxyStub + +LEAF_END CRemotingServices__CallFieldSetter, _TEXT + + +;; extern "C" ARG_SLOT __stdcall CTPMethodTable__CallTargetHelper2(const void *pTarget, +;; LPVOID pvFirst, +;; LPVOID pvSecond); +NESTED_ENTRY CTPMethodTable__CallTargetHelper2, _TEXT, CallDescrWorkerUnwindFrameChainHandler + alloc_stack 28h ;; alloc callee scratch and align the stack + END_PROLOGUE + + mov rax, rcx ; rax <- call target + mov rcx, rdx ; rcx <- first arg + mov rdx, r8 ; rdx <- second arg + + call rax + ;; It is important to have an instruction between the previous call and the epilog. + ;; If the return address is in epilog, OS won't call personality routine because + ;; it thinks personality routine does not help in this case. + nop + + ; epilog + add rsp, 28h + ret +NESTED_END CTPMethodTable__CallTargetHelper2, _TEXT + +;; extern "C" ARG_SLOT __stdcall CTPMethodTable__CallTargetHelper2(const void *pTarget, +;; LPVOID pvFirst, +;; LPVOID pvSecond, +;; LPVOID pvThird); +NESTED_ENTRY CTPMethodTable__CallTargetHelper3, _TEXT, CallDescrWorkerUnwindFrameChainHandler + alloc_stack 28h ;; alloc callee scratch and align the stack + END_PROLOGUE + + mov rax, rcx ; rax <- call target + mov rcx, rdx ; rcx <- first arg + mov rdx, r8 ; rdx <- second arg + mov r8, r9 ; r8 <- third arg + + call rax + + ;; It is important to have an instruction between the previous call and the epilog. + ;; If the return address is in epilog, OS won't call personality routine because + ;; it thinks personality routine does not help in this case. + nop + + ; epilog + add rsp, 28h + ret +NESTED_END CTPMethodTable__CallTargetHelper3, _TEXT + +NESTED_ENTRY CRemotingServices__DispatchInterfaceCall, _TEXT, CallDescrWorkerUnwindFrameChainHandler + + TRANSPARENT_PROXY_STUB_PROLOGUE + + ; + ; 'this' is a TransparentProxy. Call to stub to see if need to cross contexts. + ; + + mov rax, [rcx + TransparentProxyObject___stub] + mov rcx, [rcx + TransparentProxyObject___stubData] + call rax + + test rax, rax + jnz CrossContext + +extern VSD_GetTargetForTPWorkerQuick:proc + mov rcx, [rsp + TPSCC_PARAMS_OFFSET] ; rcx <- this + mov rdx, [rsp + TPSCC_R10_OFFSET] ; rdx <- Get the MethodDesc* or slot number + call VSD_GetTargetForTPWorkerQuick + + RESTORE_ARGUMENT_REGISTERS TPSCC_PARAMS_OFFSET + RESTORE_FLOAT_ARGUMENT_REGISTERS TPSCC_XMM_SAVE_OFFSET + + mov r10, [rsp + TPSCC_R10_OFFSET] + + test rax, rax ; Did we find a target? + jz SlowDispatch + + add rsp, TPSCC_STACK_FRAME_SIZE + TAILJMP_RAX + +SlowDispatch: + add rsp, TPSCC_STACK_FRAME_SIZE + jmp InContextTPDispatchAsmStub + +CrossContext: + RESTORE_ARGUMENT_REGISTERS TPSCC_PARAMS_OFFSET + RESTORE_FLOAT_ARGUMENT_REGISTERS TPSCC_XMM_SAVE_OFFSET + + mov r10, [rsp + TPSCC_R10_OFFSET] + + add rsp, TPSCC_STACK_FRAME_SIZE + jmp TransparentProxyStub_CrossContext + +NESTED_END CRemotingServices__DispatchInterfaceCall, _TEXT + +NESTED_ENTRY InContextTPDispatchAsmStub, _TEXT + + PROLOG_WITH_TRANSITION_BLOCK + +extern VSD_GetTargetForTPWorker:proc + lea rcx, [rsp + __PWTB_TransitionBlock] ; pTransitionBlock + mov rdx, r10 ; token + call VSD_GetTargetForTPWorker + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + TAILJMP_RAX + +NESTED_END InContextTPDispatchAsmStub, _TEXT + +endif ; FEATURE_REMOTING + + end + diff --git a/src/vm/amd64/ThePreStubAMD64.asm b/src/vm/amd64/ThePreStubAMD64.asm new file mode 100644 index 0000000000..86a9775632 --- /dev/null +++ b/src/vm/amd64/ThePreStubAMD64.asm @@ -0,0 +1,36 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +include +include AsmConstants.inc + + extern PreStubWorker:proc + extern ProcessCLRException:proc + +NESTED_ENTRY ThePreStub, _TEXT, ProcessCLRException + + PROLOG_WITH_TRANSITION_BLOCK + + ; + ; call PreStubWorker + ; + lea rcx, [rsp + __PWTB_TransitionBlock] ; pTransitionBlock* + mov rdx, METHODDESC_REGISTER + call PreStubWorker + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + TAILJMP_RAX + +NESTED_END ThePreStub, _TEXT + +LEAF_ENTRY ThePreStubPatch, _TEXT + ; make sure that the basic block is unique + test eax,34 +PATCH_LABEL ThePreStubPatchLabel + ret +LEAF_END ThePreStubPatch, _TEXT + + + +end diff --git a/src/vm/amd64/TlsGetters.asm b/src/vm/amd64/TlsGetters.asm new file mode 100644 index 0000000000..7b5a30844b --- /dev/null +++ b/src/vm/amd64/TlsGetters.asm @@ -0,0 +1,120 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; ==++== +; + +; +; ==--== +; *********************************************************************** +; File: TlsGetters.asm, see history in jithelp.asm +; +; Notes: These TlsGetters (GetAppDomain(), GetThread()) are implemented +; in a generic fashion, but might be patched at runtime to contain +; a much faster implementation which goes straight to the TLS for +; the Thread* or AppDomain*. +; +; Note that the macro takes special care to not have these become +; non-unwindable after the patching has overwritten the prologue of +; the generic getter. +; *********************************************************************** + +include AsmMacros.inc +include asmconstants.inc + +; Min amount of stack space that a nested function should allocate. +MIN_SIZE equ 28h + + +; These generic TLS getters are used for GetThread() and GetAppDomain(), they do a little +; extra work to ensure that certain registers are preserved, those include the following +; volatile registers +; +; rcx +; rdx +; r8 +; r9 +; r10 +; r11 +; +; The return value is in rax as usual +; +; They DO NOT save scratch flowing point registers, if you need those you need to save them. + +ifdef ENABLE_GET_THREAD_GENERIC_FULL_CHECK +GetThreadGenericFullCheck equ ?GetThreadGenericFullCheck@@YAPEAVThread@@XZ +extern GetThreadGenericFullCheck:proc +endif ; ENABLE_GET_THREAD_GENERIC_FULL_CHECK + +; Creates a generic TLS getter using the value from TLS slot gTLSIndex. Set GenerateGetThread +; when using this macro to generate GetThread, as that will cause special code to be generated which +; enables additional debug-only checking, such as enforcement of EE_THREAD_NOT_REQUIRED contracts +GenerateOptimizedTLSGetter macro name, GenerateGetThread + +extern g&name&TLSIndex:dword +extern __imp_TlsGetValue:qword + +SIZEOF_PUSHED_ARGS equ 10h + +NESTED_ENTRY Get&name&Generic, _TEXT + push_vol_reg r10 + push_vol_reg r11 + alloc_stack MIN_SIZE + + ; save argument registers in shadow space + save_reg_postrsp rcx, MIN_SIZE + 8h + SIZEOF_PUSHED_ARGS + save_reg_postrsp rdx, MIN_SIZE + 10h + SIZEOF_PUSHED_ARGS + save_reg_postrsp r8, MIN_SIZE + 18h + SIZEOF_PUSHED_ARGS + save_reg_postrsp r9, MIN_SIZE + 20h + SIZEOF_PUSHED_ARGS + END_PROLOGUE + +ifdef _DEBUG + cmp dword ptr [g&name&TLSIndex], -1 + jnz @F + int 3 +@@: +endif ; _DEBUG + +CALL_GET_THREAD_GENERIC_FULL_CHECK=0 + +ifdef ENABLE_GET_THREAD_GENERIC_FULL_CHECK +if GenerateGetThread + +; Generating the GetThread() tlsgetter, and GetThreadGenericFullCheck is +; defined in C (in threads.cpp). So we'll want to delegate directly to +; GetThreadGenericFullCheck, which may choose to do additional checking, such +; as enforcing EE_THREAD_NOT_REQUIRED contracts +CALL_GET_THREAD_GENERIC_FULL_CHECK=1 + +endif ; GenerateGetThread +endif ; ENABLE_GET_THREAD_GENERIC_FULL_CHECK + +if CALL_GET_THREAD_GENERIC_FULL_CHECK + call GetThreadGenericFullCheck +else + ; Not generating the GetThread() tlsgetter (or there is no GetThreadGenericFullCheck + ; to call), so do nothing special--just look up the value stored at TLS slot gTLSIndex + mov ecx, [g&name&TLSIndex] + call [__imp_TlsGetValue] +endif + + ; restore arguments from shadow space + mov rcx, [rsp + MIN_SIZE + 8h + SIZEOF_PUSHED_ARGS] + mov rdx, [rsp + MIN_SIZE + 10h + SIZEOF_PUSHED_ARGS] + mov r8, [rsp + MIN_SIZE + 18h + SIZEOF_PUSHED_ARGS] + mov r9, [rsp + MIN_SIZE + 20h + SIZEOF_PUSHED_ARGS] + + ; epilog + add rsp, MIN_SIZE + pop r11 + pop r10 + ret +NESTED_END Get&name&Generic, _TEXT + + endm + +GenerateOptimizedTLSGetter Thread, 1 +GenerateOptimizedTLSGetter AppDomain, 0 + + end diff --git a/src/vm/amd64/UMThunkStub.asm b/src/vm/amd64/UMThunkStub.asm new file mode 100644 index 0000000000..ad3f17c854 --- /dev/null +++ b/src/vm/amd64/UMThunkStub.asm @@ -0,0 +1,618 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; ==++== +; + +; +; ==--== + +include +include AsmConstants.inc + +ifdef FEATURE_MIXEDMODE +IJWNOADThunk__MakeCall equ ?MakeCall@IJWNOADThunk@@KAXXZ +IJWNOADThunk__FindThunkTarget equ ?FindThunkTarget@IJWNOADThunk@@QEAAPEBXXZ +endif +gfHostConfig equ ?g_fHostConfig@@3KA +NDirect__IsHostHookEnabled equ ?IsHostHookEnabled@NDirect@@SAHXZ + +extern CreateThreadBlockThrow:proc +extern TheUMEntryPrestubWorker:proc +ifdef FEATURE_MIXEDMODE +extern IJWNOADThunk__FindThunkTarget:proc +endif +extern UMEntryPrestubUnwindFrameChainHandler:proc +extern UMThunkStubUnwindFrameChainHandler:proc +extern g_TrapReturningThreads:dword +extern UM2MDoADCallBack:proc +extern ReverseEnterRuntimeHelper:proc +extern ReverseLeaveRuntimeHelper:proc +ifdef FEATURE_INCLUDE_ALL_INTERFACES +extern gfHostConfig:dword +extern NDirect__IsHostHookEnabled:proc +endif +extern UMThunkStubRareDisableWorker:proc +extern ReversePInvokeBadTransition:proc + +; +; METHODDESC_REGISTER: UMEntryThunk* +; +NESTED_ENTRY TheUMEntryPrestub, _TEXT, UMEntryPrestubUnwindFrameChainHandler + +TheUMEntryPrestub_STACK_FRAME_SIZE = SIZEOF_MAX_OUTGOING_ARGUMENT_HOMES + +; XMM save area +TheUMEntryPrestub_XMM_SAVE_OFFSET = TheUMEntryPrestub_STACK_FRAME_SIZE +TheUMEntryPrestub_STACK_FRAME_SIZE = TheUMEntryPrestub_STACK_FRAME_SIZE + SIZEOF_MAX_FP_ARG_SPILL + +; Ensure that the new rsp will be 16-byte aligned. Note that the caller has +; already pushed the return address. +if ((TheUMEntryPrestub_STACK_FRAME_SIZE + 8) MOD 16) ne 0 +TheUMEntryPrestub_STACK_FRAME_SIZE = TheUMEntryPrestub_STACK_FRAME_SIZE + 8 +endif + + alloc_stack TheUMEntryPrestub_STACK_FRAME_SIZE + + save_reg_postrsp rcx, TheUMEntryPrestub_STACK_FRAME_SIZE + 8h + save_reg_postrsp rdx, TheUMEntryPrestub_STACK_FRAME_SIZE + 10h + save_reg_postrsp r8, TheUMEntryPrestub_STACK_FRAME_SIZE + 18h + save_reg_postrsp r9, TheUMEntryPrestub_STACK_FRAME_SIZE + 20h + + save_xmm128_postrsp xmm0, TheUMEntryPrestub_XMM_SAVE_OFFSET + save_xmm128_postrsp xmm1, TheUMEntryPrestub_XMM_SAVE_OFFSET + 10h + save_xmm128_postrsp xmm2, TheUMEntryPrestub_XMM_SAVE_OFFSET + 20h + save_xmm128_postrsp xmm3, TheUMEntryPrestub_XMM_SAVE_OFFSET + 30h + + END_PROLOGUE + + ; + ; Do prestub-specific stuff + ; + mov rcx, METHODDESC_REGISTER + call TheUMEntryPrestubWorker + + ; + ; we're going to tail call to the exec stub that we just setup + ; + + mov rcx, [rsp + TheUMEntryPrestub_STACK_FRAME_SIZE + 8h] + mov rdx, [rsp + TheUMEntryPrestub_STACK_FRAME_SIZE + 10h] + mov r8, [rsp + TheUMEntryPrestub_STACK_FRAME_SIZE + 18h] + mov r9, [rsp + TheUMEntryPrestub_STACK_FRAME_SIZE + 20h] + + movdqa xmm0, xmmword ptr [rsp + TheUMEntryPrestub_XMM_SAVE_OFFSET] + movdqa xmm1, xmmword ptr [rsp + TheUMEntryPrestub_XMM_SAVE_OFFSET + 10h] + movdqa xmm2, xmmword ptr [rsp + TheUMEntryPrestub_XMM_SAVE_OFFSET + 20h] + movdqa xmm3, xmmword ptr [rsp + TheUMEntryPrestub_XMM_SAVE_OFFSET + 30h] + + ; + ; epilogue + ; + add rsp, TheUMEntryPrestub_STACK_FRAME_SIZE + TAILJMP_RAX + +NESTED_END TheUMEntryPrestub, _TEXT + + +; +; METHODDESC_REGISTER: UMEntryThunk* +; +NESTED_ENTRY UMThunkStub, _TEXT, UMThunkStubUnwindFrameChainHandler + +UMThunkStubAMD64_STACK_FRAME_SIZE = 0 + +; number of integer registers saved in prologue +UMThunkStubAMD64_NUM_REG_PUSHES = 2 +UMThunkStubAMD64_STACK_FRAME_SIZE = UMThunkStubAMD64_STACK_FRAME_SIZE + (UMThunkStubAMD64_NUM_REG_PUSHES * 8) + +; rare path spill area +UMThunkStubAMD64_RARE_PATH_SPILL_SIZE = 10h +UMThunkStubAMD64_STACK_FRAME_SIZE = UMThunkStubAMD64_STACK_FRAME_SIZE + UMThunkStubAMD64_RARE_PATH_SPILL_SIZE +UMThunkStubAMD64_RARE_PATH_SPILL_NEGOFFSET = UMThunkStubAMD64_STACK_FRAME_SIZE + + + +; HOST_NOTIFY_FLAG +UMThunkStubAMD64_STACK_FRAME_SIZE = UMThunkStubAMD64_STACK_FRAME_SIZE + 8 +UMThunkStubAMD64_HOST_NOTIFY_FLAG_NEGOFFSET = UMThunkStubAMD64_STACK_FRAME_SIZE + +; XMM save area +UMThunkStubAMD64_STACK_FRAME_SIZE = UMThunkStubAMD64_STACK_FRAME_SIZE + SIZEOF_MAX_FP_ARG_SPILL + +; Ensure that the offset of the XMM save area will be 16-byte aligned. +if ((UMThunkStubAMD64_STACK_FRAME_SIZE + 8) MOD 16) ne 0 ; +8 for caller-pushed return address +UMThunkStubAMD64_STACK_FRAME_SIZE = UMThunkStubAMD64_STACK_FRAME_SIZE + 8 +endif + +UMThunkStubAMD64_XMM_SAVE_NEGOFFSET = UMThunkStubAMD64_STACK_FRAME_SIZE + +; Add in the callee scratch area size. +UMThunkStubAMD64_CALLEE_SCRATCH_SIZE = SIZEOF_MAX_OUTGOING_ARGUMENT_HOMES +UMThunkStubAMD64_STACK_FRAME_SIZE = UMThunkStubAMD64_STACK_FRAME_SIZE + UMThunkStubAMD64_CALLEE_SCRATCH_SIZE + +; Now we have the full size of the stack frame. The offsets have been computed relative to the +; top, so negate them to make them relative to the post-prologue rsp. +UMThunkStubAMD64_FRAME_OFFSET = UMThunkStubAMD64_CALLEE_SCRATCH_SIZE +UMThunkStubAMD64_RARE_PATH_SPILL_OFFSET = UMThunkStubAMD64_STACK_FRAME_SIZE - UMThunkStubAMD64_FRAME_OFFSET - UMThunkStubAMD64_RARE_PATH_SPILL_NEGOFFSET +UMThunkStubAMD64_HOST_NOTIFY_FLAG_OFFSET = UMThunkStubAMD64_STACK_FRAME_SIZE - UMThunkStubAMD64_FRAME_OFFSET - UMThunkStubAMD64_HOST_NOTIFY_FLAG_NEGOFFSET +UMThunkStubAMD64_XMM_SAVE_OFFSET = UMThunkStubAMD64_STACK_FRAME_SIZE - UMThunkStubAMD64_FRAME_OFFSET - UMThunkStubAMD64_XMM_SAVE_NEGOFFSET +UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET = UMThunkStubAMD64_STACK_FRAME_SIZE + 8 - UMThunkStubAMD64_FRAME_OFFSET ; +8 for return address +UMThunkStubAMD64_FIXED_STACK_ALLOC_SIZE = UMThunkStubAMD64_STACK_FRAME_SIZE - (UMThunkStubAMD64_NUM_REG_PUSHES * 8) + +.errnz UMTHUNKSTUB_HOST_NOTIFY_FLAG_RBPOFFSET - UMThunkStubAMD64_HOST_NOTIFY_FLAG_OFFSET, update UMTHUNKSTUB_HOST_NOTIFY_FLAG_RBPOFFSET + + +; +; [ callee scratch ] <-- new RSP +; [ callee scratch ] +; [ callee scratch ] +; [ callee scratch ] +; {optional stack args passed to callee} +; xmm0 <-- RBP +; xmm1 +; xmm2 +; xmm3 +; {optional padding to align xmm regs} +; HOST_NOTIFY_FLAG (needs to make ReverseLeaveRuntime call flag) +; [rare path spill area] +; [rare path spill area] +; rbp save +; r12 save +; return address <-- entry RSP +; [rcx home] +; [rdx home] +; [r8 home] +; [r9 home] +; stack arg 0 +; stack arg 1 +; ... + + push_nonvol_reg r12 + push_nonvol_reg rbp ; stack_args + alloc_stack UMThunkStubAMD64_FIXED_STACK_ALLOC_SIZE + set_frame rbp, UMThunkStubAMD64_FRAME_OFFSET ; stack_args + mov byte ptr [rbp + UMThunkStubAMD64_HOST_NOTIFY_FLAG_OFFSET], 0 ; hosted + END_PROLOGUE + + ; + ; Call GetThread() + ; + CALL_GETTHREAD ; will not trash r10 + test rax, rax + jz DoThreadSetup + +HaveThread: + + mov r12, rax ; r12 <- Thread* + + ;FailFast if a native callable method invoked via ldftn and calli. + cmp dword ptr [r12 + OFFSETOF__Thread__m_fPreemptiveGCDisabled], 1 + jz InvalidTransition + + ; + ; disable preemptive GC + ; + mov dword ptr [r12 + OFFSETOF__Thread__m_fPreemptiveGCDisabled], 1 + + ; + ; catch returning thread here if a GC is in progress + ; + cmp [g_TrapReturningThreads], 0 + jnz DoTrapReturningThreadsTHROW + +InCooperativeMode: + +ifdef FEATURE_INCLUDE_ALL_INTERFACES + test [gfHostConfig], ASM_CLRTASKHOSTED ; inlined NDirect::IsHostHookEnabled ; hosted +ifdef _DEBUG + call IsHostHookEnabledHelper + test eax, eax +endif ; _DEBUG + jnz NotifyHost_ReverseEnterRuntime ; hosted +Done_NotifyHost_ReverseEnterRuntime: +endif + + mov rax, [r12 + OFFSETOF__Thread__m_pDomain] + mov eax, [rax + OFFSETOF__AppDomain__m_dwId] + + mov r11d, [METHODDESC_REGISTER + OFFSETOF__UMEntryThunk__m_dwDomainId] + + cmp rax, r11 + jne WrongAppDomain + + mov r11, [METHODDESC_REGISTER + OFFSETOF__UMEntryThunk__m_pUMThunkMarshInfo] + mov eax, [r11 + OFFSETOF__UMThunkMarshInfo__m_cbActualArgSize] ; stack_args + test rax, rax ; stack_args + jnz CopyStackArgs ; stack_args + +ArgumentsSetup: + + mov rax, [r11 + OFFSETOF__UMThunkMarshInfo__m_pILStub] ; rax <- Stub* + call rax + +PostCall: + ; + ; enable preemptive GC + ; + mov dword ptr [r12 + OFFSETOF__Thread__m_fPreemptiveGCDisabled], 0 + +ifdef FEATURE_INCLUDE_ALL_INTERFACES + cmp byte ptr [rbp + UMThunkStubAMD64_HOST_NOTIFY_FLAG_OFFSET], 0 ; hosted + jnz NotifyHost_ReverseLeaveRuntime ; hosted +Done_NotifyHost_ReverseLeaveRuntime: +endif + + ; epilog + lea rsp, [rbp - UMThunkStubAMD64_FRAME_OFFSET + UMThunkStubAMD64_FIXED_STACK_ALLOC_SIZE] + pop rbp ; stack_args + pop r12 + ret + + +DoThreadSetup: + mov [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 0h], rcx + mov [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 8h], rdx + mov [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 10h], r8 + mov [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 18h], r9 + + ; @CONSIDER: mark UMEntryThunks that have FP params and only save/restore xmm regs on those calls + ; initial measurements indidcate that this could be worth about a 5% savings in reverse + ; pinvoke overhead. + movdqa xmmword ptr[rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 0h], xmm0 + movdqa xmmword ptr[rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 10h], xmm1 + movdqa xmmword ptr[rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 20h], xmm2 + movdqa xmmword ptr[rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 30h], xmm3 + + mov [rbp + UMThunkStubAMD64_RARE_PATH_SPILL_OFFSET], METHODDESC_REGISTER + call CreateThreadBlockThrow + mov METHODDESC_REGISTER, [rbp + UMThunkStubAMD64_RARE_PATH_SPILL_OFFSET] + + mov rcx, [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 0h] + mov rdx, [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 8h] + mov r8, [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 10h] + mov r9, [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 18h] + + ; @CONSIDER: mark UMEntryThunks that have FP params and only save/restore xmm regs on those calls + movdqa xmm0, xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 0h] + movdqa xmm1, xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 10h] + movdqa xmm2, xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 20h] + movdqa xmm3, xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 30h] + + jmp HaveThread + +InvalidTransition: + ; ReversePInvokeBadTransition will failfast + call ReversePInvokeBadTransition + +DoTrapReturningThreadsTHROW: + + mov [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 0h], rcx + mov [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 8h], rdx + mov [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 10h], r8 + mov [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 18h], r9 + + ; @CONSIDER: mark UMEntryThunks that have FP params and only save/restore xmm regs on those calls + ; initial measurements indidcate that this could be worth about a 5% savings in reverse + ; pinvoke overhead. + movdqa xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 0h], xmm0 + movdqa xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 10h], xmm1 + movdqa xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 20h], xmm2 + movdqa xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 30h], xmm3 + + mov [rbp + UMThunkStubAMD64_RARE_PATH_SPILL_OFFSET], METHODDESC_REGISTER + mov rcx, r12 ; Thread* pThread + mov rdx, METHODDESC_REGISTER ; UMEntryThunk* pUMEntry + call UMThunkStubRareDisableWorker + mov METHODDESC_REGISTER, [rbp + UMThunkStubAMD64_RARE_PATH_SPILL_OFFSET] + + mov rcx, [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 0h] + mov rdx, [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 8h] + mov r8, [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 10h] + mov r9, [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 18h] + + ; @CONSIDER: mark UMEntryThunks that have FP params and only save/restore xmm regs on those calls + movdqa xmm0, xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 0h] + movdqa xmm1, xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 10h] + movdqa xmm2, xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 20h] + movdqa xmm3, xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 30h] + + jmp InCooperativeMode + +CopyStackArgs: + ; rax = cbStackArgs (with 20h for register args subtracted out already) + + sub rsp, rax + and rsp, -16 + + mov [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 0h], rcx + mov [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 8h], rdx + mov [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 10h], r8 + + ; rax = number of bytes + + lea rcx, [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + SIZEOF_MAX_OUTGOING_ARGUMENT_HOMES] + lea rdx, [rsp + UMThunkStubAMD64_CALLEE_SCRATCH_SIZE] + +CopyLoop: + ; rax = number of bytes + ; rcx = src + ; rdx = dest + ; r8 = sratch + + add rax, -8 + mov r8, [rcx + rax] + mov [rdx + rax], r8 + jnz CopyLoop + + mov rcx, [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 0h] + mov rdx, [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 8h] + mov r8, [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 10h] + + jmp ArgumentsSetup + +ifdef FEATURE_INCLUDE_ALL_INTERFACES +NotifyHost_ReverseEnterRuntime: + mov [rbp + UMThunkStubAMD64_RARE_PATH_SPILL_OFFSET], METHODDESC_REGISTER + + mov [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 0h], rcx + mov [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 8h], rdx + mov [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 10h], r8 + mov [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 18h], r9 + + ; @CONSIDER: mark UMEntryThunks that have FP params and only save/restore xmm regs on those calls + ; initial measurements indidcate that this could be worth about a 5% savings in reverse + ; pinvoke overhead. + movdqa xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 0h], xmm0 + movdqa xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 10h], xmm1 + movdqa xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 20h], xmm2 + movdqa xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 30h], xmm3 + + mov rcx, r12 + call ReverseEnterRuntimeHelper + mov byte ptr [rbp + UMThunkStubAMD64_HOST_NOTIFY_FLAG_OFFSET], 1 + + mov rcx, [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 0h] + mov rdx, [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 8h] + mov r8, [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 10h] + mov r9, [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 18h] + + ; @CONSIDER: mark UMEntryThunks that have FP params and only save/restore xmm regs on those calls + movdqa xmm0, xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 0h] + movdqa xmm1, xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 10h] + movdqa xmm2, xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 20h] + movdqa xmm3, xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 30h] + + mov METHODDESC_REGISTER, [rbp + UMThunkStubAMD64_RARE_PATH_SPILL_OFFSET] + + jmp Done_NotifyHost_ReverseEnterRuntime + +NotifyHost_ReverseLeaveRuntime: + + ; save rax, xmm0 + mov [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 0h], rax + movdqa xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 0h], xmm0 + + mov rcx, r12 + call ReverseLeaveRuntimeHelper + mov byte ptr [rbp + UMThunkStubAMD64_HOST_NOTIFY_FLAG_OFFSET], 0 + + ; restore rax, xmm0 + mov rax, [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 0h] + movdqa xmm0, xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 0h] + + jmp Done_NotifyHost_ReverseLeaveRuntime +endif + +WrongAppDomain: + ; + ; home register args to the stack + ; + mov [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 0h], rcx + mov [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 8h], rdx + mov [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 10h], r8 + mov [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 18h], r9 + + ; + ; save off xmm registers + ; + movdqa xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 0h], xmm0 + movdqa xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 10h], xmm1 + movdqa xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 20h], xmm2 + movdqa xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 30h], xmm3 + + ; + ; call our helper to perform the AD transtion + ; + mov rcx, METHODDESC_REGISTER + lea r8, [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET] + mov rax, [METHODDESC_REGISTER + OFFSETOF__UMEntryThunk__m_pUMThunkMarshInfo] + mov r9d, [rax + OFFSETOF__UMThunkMarshInfo__m_cbActualArgSize] + call UM2MDoADCallBack + + ; restore return value + mov rax, [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 0h] + movdqa xmm0, xmmword ptr [rbp + UMThunkStubAMD64_XMM_SAVE_OFFSET + 0h] + + jmp PostCall + +NESTED_END UMThunkStub, _TEXT + +; +; EXTERN_C void __stdcall UM2MThunk_WrapperHelper( +; void *pThunkArgs, ; rcx +; int argLen, ; rdx +; void *pAddr, ; r8 // not used +; UMEntryThunk *pEntryThunk, ; r9 +; Thread *pThread); ; [entry_sp + 28h] +; +NESTED_ENTRY UM2MThunk_WrapperHelper, _TEXT + + +UM2MThunk_WrapperHelper_STACK_FRAME_SIZE = 0 + +; number of integer registers saved in prologue +UM2MThunk_WrapperHelper_NUM_REG_PUSHES = 3 +UM2MThunk_WrapperHelper_STACK_FRAME_SIZE = UM2MThunk_WrapperHelper_STACK_FRAME_SIZE + (UM2MThunk_WrapperHelper_NUM_REG_PUSHES * 8) + +UM2MThunk_WrapperHelper_CALLEE_SCRATCH_SIZE = SIZEOF_MAX_OUTGOING_ARGUMENT_HOMES +UM2MThunk_WrapperHelper_STACK_FRAME_SIZE = UM2MThunk_WrapperHelper_STACK_FRAME_SIZE + UM2MThunk_WrapperHelper_CALLEE_SCRATCH_SIZE + +; Ensure that rsp remains 16-byte aligned +if ((UM2MThunk_WrapperHelper_STACK_FRAME_SIZE + 8) MOD 16) ne 0 ; +8 for caller-pushed return address +UM2MThunk_WrapperHelper_STACK_FRAME_SIZE = UM2MThunk_WrapperHelper_STACK_FRAME_SIZE + 8 +endif + +UM2MThunk_WrapperHelper_FRAME_OFFSET = UM2MThunk_WrapperHelper_CALLEE_SCRATCH_SIZE +UM2MThunk_WrapperHelper_FIXED_STACK_ALLOC_SIZE = UM2MThunk_WrapperHelper_STACK_FRAME_SIZE - (UM2MThunk_WrapperHelper_NUM_REG_PUSHES * 8) + + push_nonvol_reg rsi + push_nonvol_reg rdi + push_nonvol_reg rbp + alloc_stack UM2MThunk_WrapperHelper_FIXED_STACK_ALLOC_SIZE + set_frame rbp, UM2MThunk_WrapperHelper_FRAME_OFFSET + END_PROLOGUE + + ; + ; We are in cooperative mode and in the correct domain. + ; The host has also been notified that we've entered the + ; runtime. All we have left to do is to copy the stack, + ; setup the register args and then call the managed target + ; + + test rdx, rdx + jg CopyStackArgs + +ArgumentsSetup: + mov METHODDESC_REGISTER, r9 + + mov rsi, rcx ; rsi <- pThunkArgs + mov rcx, [rsi + 0h] + mov rdx, [rsi + 8h] + mov r8, [rsi + 10h] + mov r9, [rsi + 18h] + + movdqa xmm0, xmmword ptr [rsi + UMThunkStubAMD64_XMM_SAVE_OFFSET - UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 0h] + movdqa xmm1, xmmword ptr [rsi + UMThunkStubAMD64_XMM_SAVE_OFFSET - UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 10h] + movdqa xmm2, xmmword ptr [rsi + UMThunkStubAMD64_XMM_SAVE_OFFSET - UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 20h] + movdqa xmm3, xmmword ptr [rsi + UMThunkStubAMD64_XMM_SAVE_OFFSET - UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 30h] + + mov rax, [METHODDESC_REGISTER + OFFSETOF__UMEntryThunk__m_pUMThunkMarshInfo] ; rax <- UMThunkMarshInfo* + mov rax, [rax + OFFSETOF__UMThunkMarshInfo__m_pILStub] ; rax <- Stub* + call rax + + ; make sure we don't trash the return value + mov [rsi + 0h], rax + movdqa xmmword ptr [rsi + UMThunkStubAMD64_XMM_SAVE_OFFSET - UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET + 0h], xmm0 + + lea rsp, [rbp - UM2MThunk_WrapperHelper_FRAME_OFFSET + UM2MThunk_WrapperHelper_FIXED_STACK_ALLOC_SIZE] + pop rbp + pop rdi + pop rsi + ret + + +CopyStackArgs: + ; rdx = cbStackArgs (with 20h for register args subtracted out already) + ; rcx = pSrcArgStack + + sub rsp, rdx + and rsp, -16 + + mov r8, rcx + + lea rsi, [rcx + SIZEOF_MAX_OUTGOING_ARGUMENT_HOMES] + lea rdi, [rsp + UM2MThunk_WrapperHelper_CALLEE_SCRATCH_SIZE] + + mov rcx, rdx + shr rcx, 3 + + rep movsq + + mov rcx, r8 + + jmp ArgumentsSetup + +NESTED_END UM2MThunk_WrapperHelper, _TEXT + +ifdef _DEBUG +ifdef FEATURE_INCLUDE_ALL_INTERFACES + +NESTED_ENTRY IsHostHookEnabledHelper, _TEXT + + push_nonvol_reg rcx + push_nonvol_reg rdx + push_nonvol_reg r8 + push_nonvol_reg r9 + push_nonvol_reg r10 + +IsHostHookEnabledHelper_FIXED_STACK_ALLOC_SIZE = 20h + 40h + + alloc_stack IsHostHookEnabledHelper_FIXED_STACK_ALLOC_SIZE + + END_PROLOGUE + + movdqa xmmword ptr [rsp + 20h + 0h], xmm0 + movdqa xmmword ptr [rsp + 20h + 10h], xmm1 + movdqa xmmword ptr [rsp + 20h + 20h], xmm2 + movdqa xmmword ptr [rsp + 20h + 30h], xmm3 + + call NDirect__IsHostHookEnabled + + movdqa xmm0, xmmword ptr [rsp + 20h + 0h] + movdqa xmm1, xmmword ptr [rsp + 20h + 10h] + movdqa xmm2, xmmword ptr [rsp + 20h + 20h] + movdqa xmm3, xmmword ptr [rsp + 20h + 30h] + + ; epilog + add rsp, IsHostHookEnabledHelper_FIXED_STACK_ALLOC_SIZE + pop r10 + pop r9 + pop r8 + pop rdx + pop rcx + ret +NESTED_END IsHostHookEnabledHelper, _TEXT + +endif ; FEATURE_INCLUDE_ALL_INTERFACES +endif ; _DEBUG + +ifdef FEATURE_MIXEDMODE +NESTED_ENTRY IJWNOADThunk__MakeCall, _TEXT + ; METHODDESC_REGISTER = IJWNOADThunk* + + alloc_stack 68h + + save_reg_postrsp rcx, 70h + save_reg_postrsp rdx, 78h + save_reg_postrsp r8, 80h + save_reg_postrsp r9, 88h + + save_xmm128_postrsp xmm0, 20h + save_xmm128_postrsp xmm1, 30h + save_xmm128_postrsp xmm2, 40h + save_xmm128_postrsp xmm3, 50h + END_PROLOGUE + + mov rcx, METHODDESC_REGISTER + call IJWNOADThunk__FindThunkTarget + + movdqa xmm0, xmmword ptr [rsp + 20h] + movdqa xmm1, xmmword ptr [rsp + 30h] + movdqa xmm2, xmmword ptr [rsp + 40h] + movdqa xmm3, xmmword ptr [rsp + 50h] + + mov rcx, [rsp + 70h] + mov rdx, [rsp + 78h] + mov r8, [rsp + 80h] + mov r9 , [rsp + 88h] + + ; The target is in rax + add rsp, 68h + TAILJMP_RAX +NESTED_END IJWNOADThunk__MakeCall, _TEXT +endif ; FEATURE_MIXEDMODE + + end + diff --git a/src/vm/amd64/VirtualCallStubAMD64.asm b/src/vm/amd64/VirtualCallStubAMD64.asm new file mode 100644 index 0000000000..fc032dd204 --- /dev/null +++ b/src/vm/amd64/VirtualCallStubAMD64.asm @@ -0,0 +1,109 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +include +include AsmConstants.inc + +CHAIN_SUCCESS_COUNTER equ ?g_dispatch_cache_chain_success_counter@@3_KA + + extern VSD_ResolveWorker:proc + extern CHAIN_SUCCESS_COUNTER:dword + + extern StubDispatchFixupWorker:proc + extern ProcessCLRException:proc + +BACKPATCH_FLAG equ 1 ;; Also known as SDF_ResolveBackPatch in the EE +PROMOTE_CHAIN_FLAG equ 2 ;; Also known as SDF_ResolvePromoteChain in the EE +INITIAL_SUCCESS_COUNT equ 100h + +;; On Input: +;; r11 contains the address of the indirection cell (with the flags in the low bits) +;; [rsp+0] m_Datum: contains the dispatch token (slot number or MethodDesc) for the target +;; or the ResolveCacheElem when r11 has the PROMOTE_CHAIN_FLAG set +;; [rsp+8] m_ReturnAddress: contains the return address of caller to stub + +NESTED_ENTRY ResolveWorkerAsmStub, _TEXT + + PROLOG_WITH_TRANSITION_BLOCK 0, 8, r8 + + ; token stored in r8 by prolog + + lea rcx, [rsp + __PWTB_TransitionBlock] ; pTransitionBlock + mov rdx, r11 ; indirection cell + flags + mov r9, rdx + and r9, 7 ; flags + sub rdx, r9 ; indirection cell + + call VSD_ResolveWorker + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + TAILJMP_RAX + +NESTED_END ResolveWorkerAsmStub, _TEXT + +;; extern void ResolveWorkerChainLookupAsmStub() +LEAF_ENTRY ResolveWorkerChainLookupAsmStub, _TEXT +;; This will perform a quick chained lookup of the entry if the initial cache lookup fails +;; On Input: +;; rdx contains our type (MethodTable) +;; r10 contains our contract (DispatchToken) +;; r11 contains the address of the indirection (and the flags in the low two bits) +;; [rsp+0x00] contains the pointer to the ResolveCacheElem +;; [rsp+0x08] contains the saved value of rdx +;; [rsp+0x10] contains the return address of caller to stub +;; + mov rax, BACKPATCH_FLAG ;; First we check if r11 has the BACKPATCH_FLAG set + and rax, r11 ;; Set the flags based on (BACKPATCH_FLAG and r11) + pop rax ;; pop the pointer to the ResolveCacheElem from the top of stack (leaving the flags unchanged) + jnz Fail ;; If the BACKPATCH_FLAGS is set we will go directly to the ResolveWorkerAsmStub + +MainLoop: + mov rax, [rax+18h] ;; get the next entry in the chain (don't bother checking the first entry again) + test rax,rax ;; test if we hit a terminating NULL + jz Fail + + cmp rdx, [rax+00h] ;; compare our MT with the one in the ResolveCacheElem + jne MainLoop + cmp r10, [rax+08h] ;; compare our DispatchToken with one in the ResolveCacheElem + jne MainLoop +Success: + sub [CHAIN_SUCCESS_COUNTER],1 ;; decrement success counter + jl Promote + mov rax, [rax+10h] ;; get the ImplTarget + pop rdx + jmp rax + +Promote: ;; Move this entry to head postion of the chain + ;; be quick to reset the counter so we don't get a bunch of contending threads + mov [CHAIN_SUCCESS_COUNTER], INITIAL_SUCCESS_COUNT + or r11, PROMOTE_CHAIN_FLAG + mov r10, rax ;; We pass the ResolveCacheElem to ResolveWorkerAsmStub instead of the DispatchToken +Fail: + pop rdx ;; Restore the original saved rdx value + push r10 ;; pass the DispatchToken or ResolveCacheElem to promote to ResolveWorkerAsmStub + + jmp ResolveWorkerAsmStub + +LEAF_END ResolveWorkerChainLookupAsmStub, _TEXT + + +NESTED_ENTRY StubDispatchFixupStub, _TEXT, ProcessCLRException + + PROLOG_WITH_TRANSITION_BLOCK + + lea rcx, [rsp + __PWTB_TransitionBlock] ; pTransitionBlock + mov rdx, r11 ; indirection cell address + + mov r8,0 ; sectionIndex + mov r9,0 ; pModule + + call StubDispatchFixupWorker + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL +PATCH_LABEL StubDispatchFixupPatchLabel + TAILJMP_RAX + +NESTED_END StubDispatchFixupStub, _TEXT + + end diff --git a/src/vm/amd64/asmconstants.h b/src/vm/amd64/asmconstants.h new file mode 100644 index 0000000000..32b23c83c3 --- /dev/null +++ b/src/vm/amd64/asmconstants.h @@ -0,0 +1,702 @@ +// 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. +// +// See makefile.inc. During the build, this file is converted into a .inc +// file for inclusion by .asm files. The #defines are converted into EQU's. +// +// Allow multiple inclusion. + + +#ifndef _TARGET_AMD64_ +#error this file should only be used on an AMD64 platform +#endif // _TARGET_AMD64_ + +#include "../../inc/switches.h" + +#ifndef ASMCONSTANTS_C_ASSERT +#define ASMCONSTANTS_C_ASSERT(cond) +#endif + +#ifndef ASMCONSTANTS_RUNTIME_ASSERT +#define ASMCONSTANTS_RUNTIME_ASSERT(cond) +#endif + + +// Some contants are different in _DEBUG builds. This macro factors out +// ifdefs from below. +#ifdef _DEBUG +#define DBG_FRE(dbg,fre) dbg +#else +#define DBG_FRE(dbg,fre) fre +#endif + +#define DynamicHelperFrameFlags_Default 0 +#define DynamicHelperFrameFlags_ObjectArg 1 +#define DynamicHelperFrameFlags_ObjectArg2 2 + +#define ASMCONSTANT_OFFSETOF_ASSERT(struct, member) \ +ASMCONSTANTS_C_ASSERT(OFFSETOF__##struct##__##member == offsetof(struct, member)); + +#define ASMCONSTANT_SIZEOF_ASSERT(classname) \ +ASMCONSTANTS_C_ASSERT(SIZEOF__##classname == sizeof(classname)); + +#define ASM_ELEMENT_TYPE_R4 0xC +ASMCONSTANTS_C_ASSERT(ASM_ELEMENT_TYPE_R4 == ELEMENT_TYPE_R4); + +#define ASM_ELEMENT_TYPE_R8 0xD +ASMCONSTANTS_C_ASSERT(ASM_ELEMENT_TYPE_R8 == ELEMENT_TYPE_R8); + +#ifdef FEATURE_INCLUDE_ALL_INTERFACES +#define ASM_CLRTASKHOSTED 0x2 +ASMCONSTANTS_C_ASSERT(ASM_CLRTASKHOSTED == CLRTASKHOSTED); +#endif + +#define METHODDESC_REGNUM 10 +#define METHODDESC_REGISTER r10 + +#define PINVOKE_CALLI_TARGET_REGNUM 10 +#define PINVOKE_CALLI_TARGET_REGISTER r10 + +#define PINVOKE_CALLI_SIGTOKEN_REGNUM 11 +#define PINVOKE_CALLI_SIGTOKEN_REGISTER r11 + +#ifdef UNIX_AMD64_ABI +// rdi, rsi, rdx, rcx, r8, r9 +#define SIZEOF_MAX_INT_ARG_SPILL 0x30 +// xmm0...xmm7 +#define SIZEOF_MAX_FP_ARG_SPILL 0x80 +#else +// rcx, rdx, r8, r9 +#define SIZEOF_MAX_OUTGOING_ARGUMENT_HOMES 0x20 +// xmm0...xmm3 +#define SIZEOF_MAX_FP_ARG_SPILL 0x40 +#endif + + + +#ifndef UNIX_AMD64_ABI +#define SIZEOF_CalleeSavedRegisters 0x40 +ASMCONSTANTS_C_ASSERT(SIZEOF_CalleeSavedRegisters == sizeof(CalleeSavedRegisters)); +#else +#define SIZEOF_CalleeSavedRegisters 0x30 +ASMCONSTANTS_C_ASSERT(SIZEOF_CalleeSavedRegisters == sizeof(CalleeSavedRegisters)); +#endif + +#define SIZEOF_GSCookie 0x8 +ASMCONSTANTS_C_ASSERT(SIZEOF_GSCookie == sizeof(GSCookie)); + +#define OFFSETOF__Frame____VFN_table 0 + +#define OFFSETOF__Frame__m_Next 0x8 +ASMCONSTANTS_C_ASSERT(OFFSETOF__Frame__m_Next + == offsetof(Frame, m_Next)); + +#define SIZEOF__Frame 0x10 + +#ifdef FEATURE_COMINTEROP +#define SIZEOF__ComPrestubMethodFrame 0x20 +ASMCONSTANTS_C_ASSERT(SIZEOF__ComPrestubMethodFrame + == sizeof(ComPrestubMethodFrame)); + +#define SIZEOF__ComMethodFrame 0x20 +ASMCONSTANTS_C_ASSERT(SIZEOF__ComMethodFrame + == sizeof(ComMethodFrame)); +#endif // FEATURE_COMINTEROP + +#define OFFSETOF__UMEntryThunk__m_pUMThunkMarshInfo 0x18 +ASMCONSTANTS_C_ASSERT(OFFSETOF__UMEntryThunk__m_pUMThunkMarshInfo + == offsetof(UMEntryThunk, m_pUMThunkMarshInfo)); + +#define OFFSETOF__UMEntryThunk__m_dwDomainId 0x20 +ASMCONSTANTS_C_ASSERT(OFFSETOF__UMEntryThunk__m_dwDomainId + == offsetof(UMEntryThunk, m_dwDomainId)); + +#define OFFSETOF__UMThunkMarshInfo__m_pILStub 0x00 +ASMCONSTANTS_C_ASSERT(OFFSETOF__UMThunkMarshInfo__m_pILStub + == offsetof(UMThunkMarshInfo, m_pILStub)); + +#define OFFSETOF__UMThunkMarshInfo__m_cbActualArgSize 0x08 +ASMCONSTANTS_C_ASSERT(OFFSETOF__UMThunkMarshInfo__m_cbActualArgSize + == offsetof(UMThunkMarshInfo, m_cbActualArgSize)); + +#ifdef FEATURE_COMINTEROP + +#define OFFSETOF__ComPlusCallMethodDesc__m_pComPlusCallInfo DBG_FRE(0x30, 0x08) +ASMCONSTANTS_C_ASSERT(OFFSETOF__ComPlusCallMethodDesc__m_pComPlusCallInfo + == offsetof(ComPlusCallMethodDesc, m_pComPlusCallInfo)); + +#define OFFSETOF__ComPlusCallInfo__m_pILStub 0x0 +ASMCONSTANTS_C_ASSERT(OFFSETOF__ComPlusCallInfo__m_pILStub + == offsetof(ComPlusCallInfo, m_pILStub)); + +#endif // FEATURE_COMINTEROP + +#define OFFSETOF__Thread__m_fPreemptiveGCDisabled 0x0C +#ifndef CROSSGEN_COMPILE +ASMCONSTANTS_C_ASSERT(OFFSETOF__Thread__m_fPreemptiveGCDisabled + == offsetof(Thread, m_fPreemptiveGCDisabled)); +#endif +#define Thread_m_fPreemptiveGCDisabled OFFSETOF__Thread__m_fPreemptiveGCDisabled + +#define OFFSETOF__Thread__m_pFrame 0x10 +#ifndef CROSSGEN_COMPILE +ASMCONSTANTS_C_ASSERT(OFFSETOF__Thread__m_pFrame + == offsetof(Thread, m_pFrame)); +#endif +#define Thread_m_pFrame OFFSETOF__Thread__m_pFrame + +#ifndef CROSSGEN_COMPILE +#define OFFSETOF__Thread__m_State 0x8 +ASMCONSTANTS_C_ASSERT(OFFSETOF__Thread__m_State + == offsetof(Thread, m_State)); + +#define OFFSETOF__Thread__m_pDomain 0x20 +ASMCONSTANTS_C_ASSERT(OFFSETOF__Thread__m_pDomain + == offsetof(Thread, m_pDomain)); + +#define OFFSETOF__Thread__m_dwLockCount 0x28 +ASMCONSTANTS_C_ASSERT(OFFSETOF__Thread__m_dwLockCount + == offsetof(Thread, m_dwLockCount)); + +#define OFFSETOF__Thread__m_ThreadId 0x2C +ASMCONSTANTS_C_ASSERT(OFFSETOF__Thread__m_ThreadId + == offsetof(Thread, m_ThreadId)); + +#define OFFSET__Thread__m_alloc_context__alloc_ptr 0x60 +ASMCONSTANTS_C_ASSERT(OFFSET__Thread__m_alloc_context__alloc_ptr == offsetof(Thread, m_alloc_context) + offsetof(alloc_context, alloc_ptr)); + +#define OFFSET__Thread__m_alloc_context__alloc_limit 0x68 +ASMCONSTANTS_C_ASSERT(OFFSET__Thread__m_alloc_context__alloc_limit == offsetof(Thread, m_alloc_context) + offsetof(alloc_context, alloc_limit)); + +#define OFFSETOF__ThreadExceptionState__m_pCurrentTracker 0x000 +ASMCONSTANTS_C_ASSERT(OFFSETOF__ThreadExceptionState__m_pCurrentTracker + == offsetof(ThreadExceptionState, m_pCurrentTracker)); + +#define THREAD_CATCHATSAFEPOINT_BITS 0x5F +ASMCONSTANTS_C_ASSERT(THREAD_CATCHATSAFEPOINT_BITS == Thread::TS_CatchAtSafePoint); +#endif // CROSSGEN_COMPILE + + +#ifdef FEATURE_REMOTING +#define TransparentProxyObject___stubData 0x10 +ASMCONSTANTS_C_ASSERT(TransparentProxyObject___stubData == offsetof(TransparentProxyObject, _stubData)) + +#define TransparentProxyObject___stub 0x28 +ASMCONSTANTS_C_ASSERT(TransparentProxyObject___stub == offsetof(TransparentProxyObject, _stub)) + +#define TransparentProxyObject___pMT 0x18 +ASMCONSTANTS_C_ASSERT(TransparentProxyObject___pMT == offsetof(TransparentProxyObject, _pMT)) +#endif // FEATURE_REMOTING + +#define OFFSETOF__NDirectMethodDesc__m_pWriteableData DBG_FRE(0x48, 0x20) +ASMCONSTANTS_C_ASSERT(OFFSETOF__NDirectMethodDesc__m_pWriteableData == offsetof(NDirectMethodDesc, ndirect.m_pWriteableData)); + +#define OFFSETOF__ObjHeader__SyncBlkIndex 0x4 +ASMCONSTANTS_C_ASSERT(OFFSETOF__ObjHeader__SyncBlkIndex + == (sizeof(ObjHeader) - offsetof(ObjHeader, m_SyncBlockValue))); + +#define SIZEOF__SyncTableEntry 0x10 +ASMCONSTANT_SIZEOF_ASSERT(SyncTableEntry); + +#define OFFSETOF__SyncTableEntry__m_SyncBlock 0x0 +ASMCONSTANT_OFFSETOF_ASSERT(SyncTableEntry, m_SyncBlock); + +#define OFFSETOF__SyncBlock__m_Monitor 0x0 +ASMCONSTANT_OFFSETOF_ASSERT(SyncBlock, m_Monitor); + +#define OFFSETOF__DelegateObject___methodPtr 0x18 +ASMCONSTANT_OFFSETOF_ASSERT(DelegateObject, _methodPtr); + +#define OFFSETOF__DelegateObject___target 0x08 +ASMCONSTANT_OFFSETOF_ASSERT(DelegateObject, _target); + +#define OFFSETOF__AwareLock__m_MonitorHeld 0x0 +ASMCONSTANTS_C_ASSERT(OFFSETOF__AwareLock__m_MonitorHeld + == offsetof(AwareLock, m_MonitorHeld)); + +#define OFFSETOF__AwareLock__m_Recursion 0x4 +ASMCONSTANTS_C_ASSERT(OFFSETOF__AwareLock__m_Recursion + == offsetof(AwareLock, m_Recursion)); + +#define OFFSETOF__AwareLock__m_HoldingThread 0x8 +ASMCONSTANTS_C_ASSERT(OFFSETOF__AwareLock__m_HoldingThread + == offsetof(AwareLock, m_HoldingThread)); + +#define OFFSETOF__g_SystemInfo__dwNumberOfProcessors 0x20 +ASMCONSTANTS_C_ASSERT(OFFSETOF__g_SystemInfo__dwNumberOfProcessors + == offsetof(SYSTEM_INFO, dwNumberOfProcessors)); + +#define OFFSETOF__g_SpinConstants__dwInitialDuration 0x0 +ASMCONSTANTS_C_ASSERT(OFFSETOF__g_SpinConstants__dwInitialDuration + == offsetof(SpinConstants, dwInitialDuration)); + +#define OFFSETOF__g_SpinConstants__dwMaximumDuration 0x4 +ASMCONSTANTS_C_ASSERT(OFFSETOF__g_SpinConstants__dwMaximumDuration + == offsetof(SpinConstants, dwMaximumDuration)); + +#define OFFSETOF__g_SpinConstants__dwBackoffFactor 0x8 +ASMCONSTANTS_C_ASSERT(OFFSETOF__g_SpinConstants__dwBackoffFactor + == offsetof(SpinConstants, dwBackoffFactor)); + +#define OFFSETOF__MethodTable__m_dwFlags 0x00 +ASMCONSTANTS_C_ASSERT(OFFSETOF__MethodTable__m_dwFlags + == offsetof(MethodTable, m_dwFlags)); + +#define OFFSET__MethodTable__m_BaseSize 0x04 +ASMCONSTANTS_C_ASSERT(OFFSET__MethodTable__m_BaseSize + == offsetof(MethodTable, m_BaseSize)); + +#define OFFSETOF__MethodTable__m_wNumInterfaces 0x0E +ASMCONSTANTS_C_ASSERT(OFFSETOF__MethodTable__m_wNumInterfaces + == offsetof(MethodTable, m_wNumInterfaces)); + +#define OFFSETOF__MethodTable__m_pParentMethodTable DBG_FRE(0x18, 0x10) +ASMCONSTANTS_C_ASSERT(OFFSETOF__MethodTable__m_pParentMethodTable + == offsetof(MethodTable, m_pParentMethodTable)); + +#define OFFSETOF__MethodTable__m_pWriteableData DBG_FRE(0x28, 0x20) +ASMCONSTANTS_C_ASSERT(OFFSETOF__MethodTable__m_pWriteableData + == offsetof(MethodTable, m_pWriteableData)); + +#define OFFSETOF__MethodTable__m_pEEClass DBG_FRE(0x30, 0x28) +ASMCONSTANTS_C_ASSERT(OFFSETOF__MethodTable__m_pEEClass + == offsetof(MethodTable, m_pEEClass)); + +#define METHODTABLE_OFFSET_VTABLE DBG_FRE(0x48, 0x40) +ASMCONSTANTS_C_ASSERT(METHODTABLE_OFFSET_VTABLE == sizeof(MethodTable)); + +#define OFFSETOF__MethodTable__m_ElementType DBG_FRE(0x38, 0x30) +ASMCONSTANTS_C_ASSERT(OFFSETOF__MethodTable__m_ElementType + == offsetof(MethodTable, m_pMultipurposeSlot1)); + +#define OFFSETOF__MethodTable__m_pInterfaceMap DBG_FRE(0x40, 0x38) +ASMCONSTANTS_C_ASSERT(OFFSETOF__MethodTable__m_pInterfaceMap + == offsetof(MethodTable, m_pMultipurposeSlot2)); + + +#define MethodTable_VtableSlotsPerChunk 8 +ASMCONSTANTS_C_ASSERT(MethodTable_VtableSlotsPerChunk == VTABLE_SLOTS_PER_CHUNK) + +#define MethodTable_VtableSlotsPerChunkLog2 3 +ASMCONSTANTS_C_ASSERT(MethodTable_VtableSlotsPerChunkLog2 == VTABLE_SLOTS_PER_CHUNK_LOG2) + +#if defined(FEATURE_TYPEEQUIVALENCE) || defined(FEATURE_REMOTING) +#define METHODTABLE_EQUIVALENCE_FLAGS 0x02000000 +ASMCONSTANTS_C_ASSERT(METHODTABLE_EQUIVALENCE_FLAGS + == MethodTable::enum_flag_HasTypeEquivalence); +#else +#define METHODTABLE_EQUIVALENCE_FLAGS 0x0 +#endif + +#define METHODTABLE_NONTRIVIALINTERFACECAST_FLAGS (0x00080000 + 0x40000000 + 0x00400000) +ASMCONSTANTS_C_ASSERT(METHODTABLE_NONTRIVIALINTERFACECAST_FLAGS + == MethodTable::enum_flag_NonTrivialInterfaceCast); + +#define MethodTable__enum_flag_ContainsPointers 0x01000000 +ASMCONSTANTS_C_ASSERT(MethodTable__enum_flag_ContainsPointers + == MethodTable::enum_flag_ContainsPointers); + +#define OFFSETOF__MethodTableWriteableData__m_dwFlags 0 +ASMCONSTANTS_C_ASSERT(OFFSETOF__MethodTableWriteableData__m_dwFlags + == offsetof(MethodTableWriteableData, m_dwFlags)); + +#define MethodTableWriteableData__enum_flag_Unrestored 0x04 +ASMCONSTANTS_C_ASSERT(MethodTableWriteableData__enum_flag_Unrestored + == MethodTableWriteableData::enum_flag_Unrestored); + +#define OFFSETOF__InterfaceInfo_t__m_pMethodTable 0 +ASMCONSTANTS_C_ASSERT(OFFSETOF__InterfaceInfo_t__m_pMethodTable + == offsetof(InterfaceInfo_t, m_pMethodTable)); + +#define SIZEOF__InterfaceInfo_t 0x8 +ASMCONSTANTS_C_ASSERT(SIZEOF__InterfaceInfo_t + == sizeof(InterfaceInfo_t)); + +#define OFFSETOF__AppDomain__m_dwId 0x8 +ASMCONSTANTS_C_ASSERT(OFFSETOF__AppDomain__m_dwId + == offsetof(AppDomain, m_dwId)); + +#define OFFSETOF__AppDomain__m_sDomainLocalBlock DBG_FRE(0x10, 0x10) +ASMCONSTANTS_C_ASSERT(OFFSETOF__AppDomain__m_sDomainLocalBlock + == offsetof(AppDomain, m_sDomainLocalBlock)); + +#define OFFSETOF__DomainLocalBlock__m_pModuleSlots 0x8 +ASMCONSTANTS_C_ASSERT(OFFSETOF__DomainLocalBlock__m_pModuleSlots + == offsetof(DomainLocalBlock, m_pModuleSlots)); + +#define OFFSETOF__DomainLocalModule__m_pDataBlob 0x030 +ASMCONSTANTS_C_ASSERT(OFFSETOF__DomainLocalModule__m_pDataBlob + == offsetof(DomainLocalModule, m_pDataBlob)); + +// If this changes then we can't just test one bit in the assembly code. +ASMCONSTANTS_C_ASSERT(ClassInitFlags::INITIALIZED_FLAG == 1); + +// End for JIT_GetSharedNonGCStaticBaseWorker + +// For JIT_GetSharedGCStaticBaseWorker + +#define OFFSETOF__DomainLocalModule__m_pGCStatics 0x020 +ASMCONSTANTS_C_ASSERT(OFFSETOF__DomainLocalModule__m_pGCStatics + == offsetof(DomainLocalModule, m_pGCStatics)); + +// End for JIT_GetSharedGCStaticBaseWorker + +#define CORINFO_NullReferenceException_ASM 0 +ASMCONSTANTS_C_ASSERT( CORINFO_NullReferenceException_ASM + == CORINFO_NullReferenceException); + +#define CORINFO_InvalidCastException_ASM 2 +ASMCONSTANTS_C_ASSERT( CORINFO_InvalidCastException_ASM + == CORINFO_InvalidCastException); + +#define CORINFO_IndexOutOfRangeException_ASM 3 +ASMCONSTANTS_C_ASSERT( CORINFO_IndexOutOfRangeException_ASM + == CORINFO_IndexOutOfRangeException); + +#define CORINFO_SynchronizationLockException_ASM 5 +ASMCONSTANTS_C_ASSERT( CORINFO_SynchronizationLockException_ASM + == CORINFO_SynchronizationLockException); + +#define CORINFO_ArrayTypeMismatchException_ASM 6 +ASMCONSTANTS_C_ASSERT( CORINFO_ArrayTypeMismatchException_ASM + == CORINFO_ArrayTypeMismatchException); + +#define CORINFO_ArgumentNullException_ASM 8 +ASMCONSTANTS_C_ASSERT( CORINFO_ArgumentNullException_ASM + == CORINFO_ArgumentNullException); + +#define CORINFO_ArgumentException_ASM 9 +ASMCONSTANTS_C_ASSERT( CORINFO_ArgumentException_ASM + == CORINFO_ArgumentException); + + +// MachState offsets (AMD64\gmscpu.h) + +#define OFFSETOF__MachState__m_Rip 0x00 +ASMCONSTANTS_C_ASSERT(OFFSETOF__MachState__m_Rip + == offsetof(MachState, m_Rip)); + +#define OFFSETOF__MachState__m_Rsp 0x08 +ASMCONSTANTS_C_ASSERT(OFFSETOF__MachState__m_Rsp + == offsetof(MachState, m_Rsp)); + +#define OFFSETOF__MachState__m_Capture 0x10 +ASMCONSTANTS_C_ASSERT(OFFSETOF__MachState__m_Capture + == offsetof(MachState, m_Capture)); + +#ifdef UNIX_AMD64_ABI +#define OFFSETOF__MachState__m_Ptrs 0x40 +#define OFFSETOF__MachState___pRetAddr 0x70 +#define OFFSETOF__LazyMachState__m_CaptureRip 0xA8 +#define OFFSETOF__LazyMachState__m_CaptureRsp 0xB0 +#else +#define OFFSETOF__MachState__m_Ptrs 0x50 +#define OFFSETOF__MachState___pRetAddr 0x90 +#define OFFSETOF__LazyMachState__m_CaptureRip 0x98 +#define OFFSETOF__LazyMachState__m_CaptureRsp 0xA0 +#endif +ASMCONSTANTS_C_ASSERT(OFFSETOF__MachState__m_Ptrs + == offsetof(MachState, m_Ptrs)); +ASMCONSTANTS_C_ASSERT(OFFSETOF__MachState___pRetAddr + == offsetof(MachState, _pRetAddr)); +ASMCONSTANTS_C_ASSERT(OFFSETOF__LazyMachState__m_CaptureRip + == offsetof(LazyMachState, m_CaptureRip)); +ASMCONSTANTS_C_ASSERT(OFFSETOF__LazyMachState__m_CaptureRsp + == offsetof(LazyMachState, m_CaptureRsp)); + +#define OFFSETOF__MethodDesc__m_wFlags DBG_FRE(0x2E, 0x06) +ASMCONSTANTS_C_ASSERT(OFFSETOF__MethodDesc__m_wFlags == offsetof(MethodDesc, m_wFlags)); + +#define OFFSETOF__VASigCookie__pNDirectILStub 0x8 +ASMCONSTANTS_C_ASSERT(OFFSETOF__VASigCookie__pNDirectILStub + == offsetof(VASigCookie, pNDirectILStub)); + +#define SIZEOF__CONTEXT (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*16 + 8 + /*XMM_SAVE_AREA32*/(2*2 + 1*2 + 2 + 4 + 2*2 + 4 + 2*2 + 4*2 + 16*8 + 16*16 + 1*96) + 26*16 + 8 + 8*5) +ASMCONSTANTS_C_ASSERT(SIZEOF__CONTEXT + == sizeof(CONTEXT)); + +#define OFFSETOF__CONTEXT__Rax (8*6 + 4*2 + 2*6 + 4 + 8*6) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Rax + == offsetof(CONTEXT, Rax)); + +#define OFFSETOF__CONTEXT__Rcx (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Rcx + == offsetof(CONTEXT, Rcx)); + +#define OFFSETOF__CONTEXT__Rdx (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*2) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Rdx + == offsetof(CONTEXT, Rdx)); + +#define OFFSETOF__CONTEXT__Rbx (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*3) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Rbx + == offsetof(CONTEXT, Rbx)); + +#define OFFSETOF__CONTEXT__Rsp (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*4) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Rsp + == offsetof(CONTEXT, Rsp)); + +#define OFFSETOF__CONTEXT__Rbp (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*5) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Rbp + == offsetof(CONTEXT, Rbp)); + +#define OFFSETOF__CONTEXT__Rsi (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*6) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Rsi + == offsetof(CONTEXT, Rsi)); + +#define OFFSETOF__CONTEXT__Rdi (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*7) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Rdi + == offsetof(CONTEXT, Rdi)); + +#define OFFSETOF__CONTEXT__R8 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*8) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__R8 + == offsetof(CONTEXT, R8)); + +#define OFFSETOF__CONTEXT__R9 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*9) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__R9 + == offsetof(CONTEXT, R9)); + +#define OFFSETOF__CONTEXT__R10 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*10) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__R10 + == offsetof(CONTEXT, R10)); + +#define OFFSETOF__CONTEXT__R11 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*11) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__R11 + == offsetof(CONTEXT, R11)); + +#define OFFSETOF__CONTEXT__R12 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*12) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__R12 + == offsetof(CONTEXT, R12)); + +#define OFFSETOF__CONTEXT__R13 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*13) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__R13 + == offsetof(CONTEXT, R13)); + +#define OFFSETOF__CONTEXT__R14 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*14) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__R14 + == offsetof(CONTEXT, R14)); + +#define OFFSETOF__CONTEXT__R15 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*15) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__R15 + == offsetof(CONTEXT, R15)); + +#define OFFSETOF__CONTEXT__Rip (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*16) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Rip + == offsetof(CONTEXT, Rip)); + +#define OFFSETOF__CONTEXT__Xmm0 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*16 + 8 + 2*16 + 8*16) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Xmm0 + == offsetof(CONTEXT, Xmm0)); + +#define OFFSETOF__CONTEXT__Xmm1 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*16 + 8 + 2*16 + 8*16 + 16) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Xmm1 + == offsetof(CONTEXT, Xmm1)); + +#define OFFSETOF__CONTEXT__Xmm2 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*16 + 8 + 2*16 + 8*16 + 16*2) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Xmm2 + == offsetof(CONTEXT, Xmm2)); + +#define OFFSETOF__CONTEXT__Xmm3 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*16 + 8 + 2*16 + 8*16 + 16*3) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Xmm3 + == offsetof(CONTEXT, Xmm3)); + +#define OFFSETOF__CONTEXT__Xmm4 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*16 + 8 + 2*16 + 8*16 + 16*4) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Xmm4 + == offsetof(CONTEXT, Xmm4)); + +#define OFFSETOF__CONTEXT__Xmm5 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*16 + 8 + 2*16 + 8*16 + 16*5) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Xmm5 + == offsetof(CONTEXT, Xmm5)); + +#define OFFSETOF__CONTEXT__Xmm6 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*16 + 8 + 2*16 + 8*16 + 16*6) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Xmm6 + == offsetof(CONTEXT, Xmm6)); + +#define OFFSETOF__CONTEXT__Xmm7 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*16 + 8 + 2*16 + 8*16 + 16*7) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Xmm7 + == offsetof(CONTEXT, Xmm7)); + +#define OFFSETOF__CONTEXT__Xmm8 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*16 + 8 + 2*16 + 8*16 + 16*8) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Xmm8 + == offsetof(CONTEXT, Xmm8)); + +#define OFFSETOF__CONTEXT__Xmm9 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*16 + 8 + 2*16 + 8*16 + 16*9) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Xmm9 + == offsetof(CONTEXT, Xmm9)); + +#define OFFSETOF__CONTEXT__Xmm10 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*16 + 8 + 2*16 + 8*16 + 16*10) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Xmm10 + == offsetof(CONTEXT, Xmm10)); + +#define OFFSETOF__CONTEXT__Xmm11 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*16 + 8 + 2*16 + 8*16 + 16*11) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Xmm11 + == offsetof(CONTEXT, Xmm11)); + +#define OFFSETOF__CONTEXT__Xmm12 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*16 + 8 + 2*16 + 8*16 + 16*12) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Xmm12 + == offsetof(CONTEXT, Xmm12)); + +#define OFFSETOF__CONTEXT__Xmm13 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*16 + 8 + 2*16 + 8*16 + 16*13) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Xmm13 + == offsetof(CONTEXT, Xmm13)); + +#define OFFSETOF__CONTEXT__Xmm14 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*16 + 8 + 2*16 + 8*16 + 16*14) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Xmm14 + == offsetof(CONTEXT, Xmm14)); + +#define OFFSETOF__CONTEXT__Xmm15 (8*6 + 4*2 + 2*6 + 4 + 8*6 + 8*16 + 8 + 2*16 + 8*16 + 16*15) +ASMCONSTANTS_C_ASSERT(OFFSETOF__CONTEXT__Xmm15 + == offsetof(CONTEXT, Xmm15)); + +#define SIZEOF__FaultingExceptionFrame (0x20 + SIZEOF__CONTEXT) +ASMCONSTANTS_C_ASSERT(SIZEOF__FaultingExceptionFrame + == sizeof(FaultingExceptionFrame)); + +#define OFFSETOF__FaultingExceptionFrame__m_fFilterExecuted 0x10 +ASMCONSTANTS_C_ASSERT(OFFSETOF__FaultingExceptionFrame__m_fFilterExecuted + == offsetof(FaultingExceptionFrame, m_fFilterExecuted)); + +#define OFFSETOF__PtrArray__m_NumComponents 0x8 +ASMCONSTANTS_C_ASSERT(OFFSETOF__PtrArray__m_NumComponents + == offsetof(PtrArray, m_NumComponents)); + +#define OFFSETOF__PtrArray__m_Array 0x10 +ASMCONSTANTS_C_ASSERT(OFFSETOF__PtrArray__m_Array + == offsetof(PtrArray, m_Array)); + + +#define MethodDescClassification__mdcClassification 0x7 +ASMCONSTANTS_C_ASSERT(MethodDescClassification__mdcClassification == mdcClassification); + +#define MethodDescClassification__mcInstantiated 0x5 +ASMCONSTANTS_C_ASSERT(MethodDescClassification__mcInstantiated == mcInstantiated); + +#ifndef FEATURE_PAL + +#define OFFSET__TEB__TlsSlots 0x1480 +ASMCONSTANTS_C_ASSERT(OFFSET__TEB__TlsSlots == offsetof(TEB, TlsSlots)); + +#define OFFSETOF__TEB__LastErrorValue 0x68 +ASMCONSTANTS_C_ASSERT(OFFSETOF__TEB__LastErrorValue == offsetof(TEB, LastErrorValue)); + +#endif // !FEATURE_PAL + +#ifdef _DEBUG +#define TLS_GETTER_MAX_SIZE_ASM 0x30 +#else +#define TLS_GETTER_MAX_SIZE_ASM 0x18 +#endif +ASMCONSTANTS_C_ASSERT(TLS_GETTER_MAX_SIZE_ASM == TLS_GETTER_MAX_SIZE) + + +// If you change these constants, you need to update code in +// RedirectHandledJITCase.asm and ExcepAMD64.cpp. +#define REDIRECTSTUB_ESTABLISHER_OFFSET_RBP 0 +#define REDIRECTSTUB_RBP_OFFSET_CONTEXT 0x20 + +#define THROWSTUB_ESTABLISHER_OFFSET_FaultingExceptionFrame 0x30 + +#define UMTHUNKSTUB_HOST_NOTIFY_FLAG_RBPOFFSET (0x40) // xmm save size + +#define Thread__ObjectRefFlush ?ObjectRefFlush@Thread@@SAXPEAV1@@Z + + +#define DELEGATE_FIELD_OFFSET__METHOD_AUX 0x20 +ASMCONSTANTS_RUNTIME_ASSERT(DELEGATE_FIELD_OFFSET__METHOD_AUX == Object::GetOffsetOfFirstField() + + MscorlibBinder::GetFieldOffset(FIELD__DELEGATE__METHOD_PTR_AUX)); + + +#define ASM_LARGE_OBJECT_SIZE 85000 +ASMCONSTANTS_C_ASSERT(ASM_LARGE_OBJECT_SIZE == LARGE_OBJECT_SIZE); + +#define OFFSETOF__ArrayBase__m_NumComponents 8 +ASMCONSTANTS_C_ASSERT(OFFSETOF__ArrayBase__m_NumComponents + == offsetof(ArrayBase, m_NumComponents)); + +#define OFFSETOF__StringObject__m_StringLength 0x8 +ASMCONSTANTS_C_ASSERT(OFFSETOF__StringObject__m_StringLength + == offsetof(StringObject, m_StringLength)); + +#define OFFSETOF__ArrayTypeDesc__m_TemplateMT 8 +ASMCONSTANTS_C_ASSERT(OFFSETOF__ArrayTypeDesc__m_TemplateMT + == offsetof(ArrayTypeDesc, m_TemplateMT)); + +#define OFFSETOF__ArrayTypeDesc__m_Arg 0x10 +ASMCONSTANTS_C_ASSERT(OFFSETOF__ArrayTypeDesc__m_Arg + == offsetof(ArrayTypeDesc, m_Arg)); + +#define SYNCBLOCKINDEX_OFFSET 0x4 +ASMCONSTANTS_C_ASSERT(SYNCBLOCKINDEX_OFFSET + == (sizeof(ObjHeader) - offsetof(ObjHeader, m_SyncBlockValue))); + +#define CallDescrData__pSrc 0x00 +#define CallDescrData__numStackSlots 0x08 +#ifdef UNIX_AMD64_ABI +#define CallDescrData__pArgumentRegisters 0x10 +#define CallDescrData__pFloatArgumentRegisters 0x18 +#define CallDescrData__fpReturnSize 0x20 +#define CallDescrData__pTarget 0x28 +#define CallDescrData__returnValue 0x30 +#else +#define CallDescrData__dwRegTypeMap 0x10 +#define CallDescrData__fpReturnSize 0x18 +#define CallDescrData__pTarget 0x20 +#define CallDescrData__returnValue 0x28 +#endif + +ASMCONSTANTS_C_ASSERT(CallDescrData__pSrc == offsetof(CallDescrData, pSrc)) +ASMCONSTANTS_C_ASSERT(CallDescrData__numStackSlots == offsetof(CallDescrData, numStackSlots)) +#ifdef UNIX_AMD64_ABI +ASMCONSTANTS_C_ASSERT(CallDescrData__pArgumentRegisters == offsetof(CallDescrData, pArgumentRegisters)) +ASMCONSTANTS_C_ASSERT(CallDescrData__pFloatArgumentRegisters == offsetof(CallDescrData, pFloatArgumentRegisters)) +#else +ASMCONSTANTS_C_ASSERT(CallDescrData__dwRegTypeMap == offsetof(CallDescrData, dwRegTypeMap)) +#endif +ASMCONSTANTS_C_ASSERT(CallDescrData__fpReturnSize == offsetof(CallDescrData, fpReturnSize)) +ASMCONSTANTS_C_ASSERT(CallDescrData__pTarget == offsetof(CallDescrData, pTarget)) +ASMCONSTANTS_C_ASSERT(CallDescrData__returnValue == offsetof(CallDescrData, returnValue)) + +#ifdef UNIX_AMD64_ABI +#define OFFSETOF__TransitionBlock__m_argumentRegisters 0x00 +ASMCONSTANTS_C_ASSERT(OFFSETOF__TransitionBlock__m_argumentRegisters == offsetof(TransitionBlock, m_argumentRegisters)) +#endif // UNIX_AMD64_ABI + +#undef ASMCONSTANTS_RUNTIME_ASSERT +#undef ASMCONSTANTS_C_ASSERT +#ifndef UNIX_AMD64_ABI +#undef DBG_FRE +#endif // UNIX_AMD64_ABI + + +//#define USE_COMPILE_TIME_CONSTANT_FINDER // Uncomment this line to use the constant finder +#if defined(__cplusplus) && defined(USE_COMPILE_TIME_CONSTANT_FINDER) +// This class causes the compiler to emit an error with the constant we're interested in +// in the error message. This is useful if a size or offset changes. To use, comment out +// the compile-time assert that is firing, enable the constant finder, add the appropriate +// constant to find to BogusFunction(), and build. +// +// Here's a sample compiler error: +// d:\dd\clr\src\ndp\clr\src\vm\i386\asmconstants.h(326) : error C2248: 'FindCompileTimeConstant::FindCompileTimeConstant' : cannot access private member declared in class 'FindCompileTimeConstant' +// with +// [ +// N=1520 +// ] +// d:\dd\clr\src\ndp\clr\src\vm\i386\asmconstants.h(321) : see declaration of 'FindCompileTimeConstant::FindCompileTimeConstant' +// with +// [ +// N=1520 +// ] +template +class FindCompileTimeConstant +{ +private: + FindCompileTimeConstant(); +}; + +void BogusFunction() +{ + // Sample usage to generate the error + FindCompileTimeConstant bogus_variable; + FindCompileTimeConstant bogus_variable2; +} +#endif // defined(__cplusplus) && defined(USE_COMPILE_TIME_CONSTANT_FINDER) diff --git a/src/vm/amd64/calldescrworkeramd64.S b/src/vm/amd64/calldescrworkeramd64.S new file mode 100644 index 0000000000..05dd8ac8ef --- /dev/null +++ b/src/vm/amd64/calldescrworkeramd64.S @@ -0,0 +1,165 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +.intel_syntax noprefix +#include "unixasmmacros.inc" +#include "asmconstants.h" + +#define real4 dword +#define real8 qword + +//extern CallDescrWorkerUnwindFrameChainHandler:proc + +// +// EXTERN_C void FastCallFinalizeWorker(Object *obj, PCODE funcPtr); +// +NESTED_ENTRY FastCallFinalizeWorker, _TEXT, NoHandler + push_nonvol_reg rbp + mov rbp, rsp + END_PROLOGUE + + // + // RDI: already contains obj* + // RSI: address of finalizer method to call + // + + // !!!!!!!!! + // NOTE: you cannot tail call here because we must have the CallDescrWorkerUnwindFrameChainHandler + // personality routine on the stack. + // !!!!!!!!! + call rsi + xor rax, rax + + // epilog + pop_nonvol_reg rbp + ret + + +NESTED_END FastCallFinalizeWorker, _TEXT + +//extern "C" void CallDescrWorkerInternal(CallDescrData * pCallDescrData); + +NESTED_ENTRY CallDescrWorkerInternal, _TEXT, NoHandler + push_nonvol_reg rbp + mov rbp, rsp + push_nonvol_reg rbx + alloc_stack 8 // ensure proper alignment of the rsp + set_cfa_register rbp, (2*8) + END_PROLOGUE + + mov rbx, rdi // save pCallDescrData in rbx + + mov ecx, dword ptr [rbx + CallDescrData__numStackSlots] + + and ecx, ecx + jz LOCAL_LABEL(NoStackArguments) + + test ecx, 1 + jz LOCAL_LABEL(StackAligned) + push rax +LOCAL_LABEL(StackAligned): + + mov rsi, [rbx + CallDescrData__pSrc] // set source argument list address + lea rsi, [rsi + 8 * rcx] + +LOCAL_LABEL(StackCopyLoop): // copy the arguments to stack top-down to carefully probe for sufficient stack space + sub rsi, 8 + push qword ptr [rsi] + dec ecx + jnz LOCAL_LABEL(StackCopyLoop) +LOCAL_LABEL(NoStackArguments): + // All argument registers are loaded regardless of the actual number + // of arguments. + + mov rax, [rbx + CallDescrData__pArgumentRegisters] + mov rdi, [rax + 0] + mov rsi, [rax + 8] + mov rdx, [rax + 16] + mov rcx, [rax + 24] + mov r8, [rax + 32] + mov r9, [rax + 40] + + // All float argument registers are loaded regardless of the actual number + // of arguments. + + mov rax, [rbx + CallDescrData__pFloatArgumentRegisters] + and rax, rax + jz LOCAL_LABEL(NoFloatArguments) + movsd xmm0, [rax + 0] + movsd xmm1, [rax + 16] + movsd xmm2, [rax + 32] + movsd xmm3, [rax + 48] + movsd xmm4, [rax + 64] + movsd xmm5, [rax + 80] + movsd xmm6, [rax + 96] + movsd xmm7, [rax + 112] +LOCAL_LABEL(NoFloatArguments): + call qword ptr [rbx + CallDescrData__pTarget] // call target function + + // Save FP return value + + mov ecx, dword ptr [rbx + CallDescrData__fpReturnSize] + test ecx, ecx + jz LOCAL_LABEL(ReturnsInt) + + cmp ecx, 4 + je LOCAL_LABEL(ReturnsFloat) + cmp ecx, 8 + je LOCAL_LABEL(ReturnsDouble) + +#if defined(UNIX_AMD64_ABI) && defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) + // Struct with two integer eightbytes + cmp ecx, 16 + jne LOCAL_LABEL(NotTwoIntegerEightbytes) + mov qword ptr [rbx+CallDescrData__returnValue], rax + mov qword ptr [rbx+CallDescrData__returnValue + 8], rdx + jmp LOCAL_LABEL(Epilog) + +LOCAL_LABEL(NotTwoIntegerEightbytes): + // Struct with the first eightbyte SSE and the second one integer + cmp ecx, 16 + 1 + jne LOCAL_LABEL(NotFirstSSESecondIntegerEightbyte) + movsd real8 ptr [rbx+CallDescrData__returnValue], xmm0 + mov qword ptr [rbx+CallDescrData__returnValue + 8], rax + jmp LOCAL_LABEL(Epilog) + +LOCAL_LABEL(NotFirstSSESecondIntegerEightbyte): + // Struct with the first eightbyte integer and the second one SSE + cmp ecx, 16 + 2 + jne LOCAL_LABEL(NotFirstIntegerSecondSSEEightbyte) + mov qword ptr [rbx+CallDescrData__returnValue], rax + movsd real8 ptr [rbx+CallDescrData__returnValue + 8], xmm0 + jmp LOCAL_LABEL(Epilog) + +LOCAL_LABEL(NotFirstIntegerSecondSSEEightbyte): + // Struct with two SSE eightbytes + cmp ecx, 16 + 3 + jne LOCAL_LABEL(Epilog) // unexpected + movsd real8 ptr [rbx+CallDescrData__returnValue], xmm0 + movsd real8 ptr [rbx+CallDescrData__returnValue + 8], xmm1 +#endif // UNIX_AMD64_ABI && FEATURE_UNIX_AMD64_STRUCT_PASSING + + jmp LOCAL_LABEL(Epilog) + +LOCAL_LABEL(ReturnsInt): + mov qword ptr [rbx+CallDescrData__returnValue], rax + +LOCAL_LABEL(Epilog): + lea rsp, [rbp - 8] // deallocate arguments + set_cfa_register rsp, (3*8) + pop_nonvol_reg rbx + pop_nonvol_reg rbp + ret + +LOCAL_LABEL(ReturnsFloat): + movss real4 ptr [rbx+CallDescrData__returnValue], xmm0 + jmp LOCAL_LABEL(Epilog) + +LOCAL_LABEL(ReturnsDouble): + movsd real8 ptr [rbx+CallDescrData__returnValue], xmm0 + jmp LOCAL_LABEL(Epilog) + +NESTED_END CallDescrWorkerInternal, _TEXT + + diff --git a/src/vm/amd64/cgenamd64.cpp b/src/vm/amd64/cgenamd64.cpp new file mode 100644 index 0000000000..51aac1ebc6 --- /dev/null +++ b/src/vm/amd64/cgenamd64.cpp @@ -0,0 +1,1278 @@ +// 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. +// +// Various helper routines for generating AMD64 assembly code. +// + +// Precompiled Header + +#include "common.h" + +#include "stublink.h" +#include "cgensys.h" +#include "siginfo.hpp" +#include "excep.h" +#include "ecall.h" +#include "dllimport.h" +#include "dllimportcallback.h" +#include "dbginterface.h" +#include "fcall.h" +#include "array.h" +#include "virtualcallstub.h" +#include "jitinterface.h" + +#ifdef FEATURE_COMINTEROP +#include "clrtocomcall.h" +#endif // FEATURE_COMINTEROP + +void UpdateRegDisplayFromCalleeSavedRegisters(REGDISPLAY * pRD, CalleeSavedRegisters * pRegs) +{ + LIMITED_METHOD_CONTRACT; + + T_CONTEXT * pContext = pRD->pCurrentContext; +#define CALLEE_SAVED_REGISTER(regname) pContext->regname = pRegs->regname; + ENUM_CALLEE_SAVED_REGISTERS(); +#undef CALLEE_SAVED_REGISTER + + KNONVOLATILE_CONTEXT_POINTERS * pContextPointers = pRD->pCurrentContextPointers; +#define CALLEE_SAVED_REGISTER(regname) pContextPointers->regname = (PULONG64)&pRegs->regname; + ENUM_CALLEE_SAVED_REGISTERS(); +#undef CALLEE_SAVED_REGISTER +} + +void ClearRegDisplayArgumentAndScratchRegisters(REGDISPLAY * pRD) +{ + LIMITED_METHOD_CONTRACT; + + KNONVOLATILE_CONTEXT_POINTERS * pContextPointers = pRD->pCurrentContextPointers; + pContextPointers->Rax = NULL; +#ifdef UNIX_AMD64_ABI + pContextPointers->Rsi = NULL; + pContextPointers->Rdi = NULL; +#endif + pContextPointers->Rcx = NULL; + pContextPointers->Rdx = NULL; + pContextPointers->R8 = NULL; + pContextPointers->R9 = NULL; + pContextPointers->R10 = NULL; + pContextPointers->R11 = NULL; +} + +void TransitionFrame::UpdateRegDisplay(const PREGDISPLAY pRD) +{ + LIMITED_METHOD_CONTRACT; + + pRD->IsCallerContextValid = FALSE; + pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary. + + pRD->pCurrentContext->Rip = GetReturnAddress(); + pRD->pCurrentContext->Rsp = GetSP(); + + UpdateRegDisplayFromCalleeSavedRegisters(pRD, GetCalleeSavedRegisters()); + ClearRegDisplayArgumentAndScratchRegisters(pRD); + + SyncRegDisplayToCurrentContext(pRD); + + LOG((LF_GCROOTS, LL_INFO100000, "STACKWALK TransitionFrame::UpdateRegDisplay(rip:%p, rsp:%p)\n", pRD->ControlPC, pRD->SP)); +} + +#ifndef DACCESS_COMPILE + +extern "C" TADDR s_pStubHelperFrameVPtr; +TADDR s_pStubHelperFrameVPtr = StubHelperFrame::GetMethodFrameVPtr(); + +void TailCallFrame::InitFromContext(T_CONTEXT * pContext) +{ + WRAPPER_NO_CONTRACT; + +#define CALLEE_SAVED_REGISTER(regname) m_calleeSavedRegisters.regname = pContext->regname; + ENUM_CALLEE_SAVED_REGISTERS(); +#undef CALLEE_SAVED_REGISTER + + m_pGCLayout = 0; + m_ReturnAddress = pContext->Rip; +} + +#endif // !DACCESS_COMPILE + +void TailCallFrame::UpdateRegDisplay(const PREGDISPLAY pRD) +{ + LIMITED_METHOD_CONTRACT; + + pRD->IsCallerContextValid = FALSE; + pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary. + + pRD->pCurrentContext->Rip = m_ReturnAddress; + pRD->pCurrentContext->Rsp = dac_cast(this) + sizeof(*this); + + UpdateRegDisplayFromCalleeSavedRegisters(pRD, &m_calleeSavedRegisters); + ClearRegDisplayArgumentAndScratchRegisters(pRD); + + SyncRegDisplayToCurrentContext(pRD); + + LOG((LF_GCROOTS, LL_INFO100000, "STACKWALK TransitionFrame::UpdateRegDisplay(rip:%p, rsp:%p)\n", pRD->ControlPC, pRD->SP)); +} + +void InlinedCallFrame::UpdateRegDisplay(const PREGDISPLAY pRD) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; +#ifdef PROFILING_SUPPORTED + PRECONDITION(CORProfilerStackSnapshotEnabled() || InlinedCallFrame::FrameHasActiveCall(this)); +#endif + HOST_NOCALLS; + MODE_ANY; + SUPPORTS_DAC; + } + CONTRACTL_END; + + if (!InlinedCallFrame::FrameHasActiveCall(this)) + { + LOG((LF_CORDB, LL_ERROR, "WARNING: InlinedCallFrame::UpdateRegDisplay called on inactive frame %p\n", this)); + return; + } + + pRD->IsCallerContextValid = FALSE; + pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary. + + pRD->pCurrentContext->Rip = *(DWORD64 *)&m_pCallerReturnAddress; + pRD->pCurrentContext->Rsp = *(DWORD64 *)&m_pCallSiteSP; + pRD->pCurrentContext->Rbp = *(DWORD64 *)&m_pCalleeSavedFP; + + ClearRegDisplayArgumentAndScratchRegisters(pRD); + +#define CALLEE_SAVED_REGISTER(regname) pRD->pCurrentContextPointers->regname = NULL; + ENUM_CALLEE_SAVED_REGISTERS(); +#undef CALLEE_SAVED_REGISTER + + pRD->pCurrentContextPointers->Rbp = (DWORD64 *)&m_pCalleeSavedFP; + + SyncRegDisplayToCurrentContext(pRD); + + LOG((LF_GCROOTS, LL_INFO100000, "STACKWALK InlinedCallFrame::UpdateRegDisplay(rip:%p, rsp:%p)\n", pRD->ControlPC, pRD->SP)); +} + +void HelperMethodFrame::UpdateRegDisplay(const PREGDISPLAY pRD) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(m_MachState._pRetAddr == PTR_TADDR(&m_MachState.m_Rip)); + SUPPORTS_DAC; + } + CONTRACTL_END; + + pRD->IsCallerContextValid = FALSE; + pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary. + + // + // Copy the saved state from the frame to the current context. + // + + LOG((LF_GCROOTS, LL_INFO100000, "STACKWALK HelperMethodFrame::UpdateRegDisplay cached ip:%p, sp:%p\n", m_MachState.m_Rip, m_MachState.m_Rsp)); + +#if defined(DACCESS_COMPILE) + // For DAC, we may get here when the HMF is still uninitialized. + // So we may need to unwind here. + if (!m_MachState.isValid()) + { + // This allocation throws on OOM. + MachState* pUnwoundState = (MachState*)DacAllocHostOnlyInstance(sizeof(*pUnwoundState), true); + + InsureInit(false, pUnwoundState); + + pRD->pCurrentContext->Rip = pRD->ControlPC = pUnwoundState->m_Rip; + pRD->pCurrentContext->Rsp = pRD->SP = pUnwoundState->m_Rsp; + +#define CALLEE_SAVED_REGISTER(regname) pRD->pCurrentContext->regname = pUnwoundState->m_Capture.regname; + ENUM_CALLEE_SAVED_REGISTERS(); +#undef CALLEE_SAVED_REGISTER + +#define CALLEE_SAVED_REGISTER(regname) pRD->pCurrentContextPointers->regname = pUnwoundState->m_Ptrs.p##regname; + ENUM_CALLEE_SAVED_REGISTERS(); +#undef CALLEE_SAVED_REGISTER + + ClearRegDisplayArgumentAndScratchRegisters(pRD); + + return; + } +#endif // DACCESS_COMPILE + + pRD->pCurrentContext->Rip = pRD->ControlPC = m_MachState.m_Rip; + pRD->pCurrentContext->Rsp = pRD->SP = m_MachState.m_Rsp; + +#ifdef FEATURE_PAL + +#define CALLEE_SAVED_REGISTER(regname) pRD->pCurrentContext->regname = (m_MachState.m_Ptrs.p##regname != NULL) ? \ + *m_MachState.m_Ptrs.p##regname : m_MachState.m_Unwound.regname; + ENUM_CALLEE_SAVED_REGISTERS(); +#undef CALLEE_SAVED_REGISTER + +#else // FEATURE_PAL + +#define CALLEE_SAVED_REGISTER(regname) pRD->pCurrentContext->regname = *m_MachState.m_Ptrs.p##regname; + ENUM_CALLEE_SAVED_REGISTERS(); +#undef CALLEE_SAVED_REGISTER + +#endif // FEATURE_PAL + +#define CALLEE_SAVED_REGISTER(regname) pRD->pCurrentContextPointers->regname = m_MachState.m_Ptrs.p##regname; + ENUM_CALLEE_SAVED_REGISTERS(); +#undef CALLEE_SAVED_REGISTER + + // + // Clear all knowledge of scratch registers. We're skipping to any + // arbitrary point on the stack, and frames aren't required to preserve or + // keep track of these anyways. + // + + ClearRegDisplayArgumentAndScratchRegisters(pRD); +} + +void FaultingExceptionFrame::UpdateRegDisplay(const PREGDISPLAY pRD) +{ + LIMITED_METHOD_DAC_CONTRACT; + + memcpy(pRD->pCurrentContext, &m_ctx, sizeof(CONTEXT)); + + pRD->ControlPC = m_ctx.Rip; + + pRD->SP = m_ctx.Rsp; + + pRD->pCurrentContextPointers->Rax = &m_ctx.Rax; + pRD->pCurrentContextPointers->Rcx = &m_ctx.Rcx; + pRD->pCurrentContextPointers->Rdx = &m_ctx.Rdx; + pRD->pCurrentContextPointers->Rbx = &m_ctx.Rbx; + pRD->pCurrentContextPointers->Rbp = &m_ctx.Rbp; + pRD->pCurrentContextPointers->Rsi = &m_ctx.Rsi; + pRD->pCurrentContextPointers->Rdi = &m_ctx.Rdi; + pRD->pCurrentContextPointers->R8 = &m_ctx.R8; + pRD->pCurrentContextPointers->R9 = &m_ctx.R9; + pRD->pCurrentContextPointers->R10 = &m_ctx.R10; + pRD->pCurrentContextPointers->R11 = &m_ctx.R11; + pRD->pCurrentContextPointers->R12 = &m_ctx.R12; + pRD->pCurrentContextPointers->R13 = &m_ctx.R13; + pRD->pCurrentContextPointers->R14 = &m_ctx.R14; + pRD->pCurrentContextPointers->R15 = &m_ctx.R15; + + pRD->IsCallerContextValid = FALSE; + pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary. +} + +#ifdef FEATURE_HIJACK +TADDR ResumableFrame::GetReturnAddressPtr() +{ + LIMITED_METHOD_DAC_CONTRACT; + return dac_cast(m_Regs) + offsetof(CONTEXT, Rip); +} + +void ResumableFrame::UpdateRegDisplay(const PREGDISPLAY pRD) +{ + CONTRACT_VOID + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + SUPPORTS_DAC; + } + CONTRACT_END; + + CopyMemory(pRD->pCurrentContext, m_Regs, sizeof(CONTEXT)); + + pRD->ControlPC = m_Regs->Rip; + + pRD->SP = m_Regs->Rsp; + + pRD->pCurrentContextPointers->Rax = &m_Regs->Rax; + pRD->pCurrentContextPointers->Rcx = &m_Regs->Rcx; + pRD->pCurrentContextPointers->Rdx = &m_Regs->Rdx; + pRD->pCurrentContextPointers->Rbx = &m_Regs->Rbx; + pRD->pCurrentContextPointers->Rbp = &m_Regs->Rbp; + pRD->pCurrentContextPointers->Rsi = &m_Regs->Rsi; + pRD->pCurrentContextPointers->Rdi = &m_Regs->Rdi; + pRD->pCurrentContextPointers->R8 = &m_Regs->R8; + pRD->pCurrentContextPointers->R9 = &m_Regs->R9; + pRD->pCurrentContextPointers->R10 = &m_Regs->R10; + pRD->pCurrentContextPointers->R11 = &m_Regs->R11; + pRD->pCurrentContextPointers->R12 = &m_Regs->R12; + pRD->pCurrentContextPointers->R13 = &m_Regs->R13; + pRD->pCurrentContextPointers->R14 = &m_Regs->R14; + pRD->pCurrentContextPointers->R15 = &m_Regs->R15; + + pRD->IsCallerContextValid = FALSE; + pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary. + + RETURN; +} + +// The HijackFrame has to know the registers that are pushed by OnHijackTripThread +void HijackFrame::UpdateRegDisplay(const PREGDISPLAY pRD) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACTL_END; + + pRD->IsCallerContextValid = FALSE; + pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary. + + pRD->pCurrentContext->Rip = m_ReturnAddress; + pRD->pCurrentContext->Rsp = PTR_TO_MEMBER_TADDR(HijackArgs, m_Args, Rip) + sizeof(void *); + + UpdateRegDisplayFromCalleeSavedRegisters(pRD, &(m_Args->Regs)); + +#ifdef UNIX_AMD64_ABI + pRD->pCurrentContextPointers->Rsi = NULL; + pRD->pCurrentContextPointers->Rdi = NULL; +#endif + pRD->pCurrentContextPointers->Rcx = NULL; +#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING + pRD->pCurrentContextPointers->Rdx = (PULONG64)&m_Args->Rdx; +#else // FEATURE_UNIX_AMD64_STRUCT_PASSING + pRD->pCurrentContextPointers->Rdx = NULL; +#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING + pRD->pCurrentContextPointers->R8 = NULL; + pRD->pCurrentContextPointers->R9 = NULL; + pRD->pCurrentContextPointers->R10 = NULL; + pRD->pCurrentContextPointers->R11 = NULL; + + pRD->pCurrentContextPointers->Rax = (PULONG64)&m_Args->Rax; + + SyncRegDisplayToCurrentContext(pRD); + +/* + // This only describes the top-most frame + pRD->pContext = NULL; + + + pRD->PCTAddr = dac_cast(m_Args) + offsetof(HijackArgs, Rip); + //pRD->pPC = PTR_SLOT(pRD->PCTAddr); + pRD->SP = (ULONG64)(pRD->PCTAddr + sizeof(TADDR)); +*/ +} +#endif // FEATURE_HIJACK + +BOOL isJumpRel32(PCODE pCode) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + SUPPORTS_DAC; + } CONTRACTL_END; + + PTR_BYTE pbCode = PTR_BYTE(pCode); + + return 0xE9 == pbCode[0]; +} + +// +// Given the same pBuffer that was used by emitJump this +// method decodes the instructions and returns the jump target +// +PCODE decodeJump32(PCODE pBuffer) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + SUPPORTS_DAC; + } + CONTRACTL_END; + + // jmp rel32 + _ASSERTE(isJumpRel32(pBuffer)); + + return rel32Decode(pBuffer+1); +} + +BOOL isJumpRel64(PCODE pCode) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + SUPPORTS_DAC; + } CONTRACTL_END; + + PTR_BYTE pbCode = PTR_BYTE(pCode); + + return 0x48 == pbCode[0] && + 0xB8 == pbCode[1] && + 0xFF == pbCode[10] && + 0xE0 == pbCode[11]; +} + +PCODE decodeJump64(PCODE pBuffer) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + SUPPORTS_DAC; + } + CONTRACTL_END; + + // mov rax, xxx + // jmp rax + _ASSERTE(isJumpRel64(pBuffer)); + + return *PTR_UINT64(pBuffer+2); +} + +#ifdef DACCESS_COMPILE +BOOL GetAnyThunkTarget (CONTEXT *pctx, TADDR *pTarget, TADDR *pTargetMethodDesc) +{ + TADDR pThunk = GetIP(pctx); + + *pTargetMethodDesc = NULL; + + // + // Check for something generated by emitJump. + // + if (isJumpRel64(pThunk)) + { + *pTarget = decodeJump64(pThunk); + return TRUE; + } + + return FALSE; +} +#endif // DACCESS_COMPILE + + +#ifndef DACCESS_COMPILE + +// Note: This is only used on server GC on Windows. +// +// This function returns the number of logical processors on a given physical chip. If it cannot +// determine the number of logical cpus, or the machine is not populated uniformly with the same +// type of processors, this function returns 1. + +extern "C" DWORD __stdcall getcpuid(DWORD arg, unsigned char result[16]); + +// fix this if/when AMD does multicore or SMT +DWORD GetLogicalCpuCount() +{ + // No CONTRACT possible because GetLogicalCpuCount uses SEH + + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + + static DWORD val = 0; + + // cache value for later re-use + if (val) + { + return val; + } + + struct Param : DefaultCatchFilterParam + { + DWORD retVal; + } param; + param.pv = COMPLUS_EXCEPTION_EXECUTE_HANDLER; + param.retVal = 1; + + PAL_TRY(Param *, pParam, ¶m) + { + + unsigned char buffer[16]; + DWORD maxCpuId = getcpuid(0, buffer); + DWORD* dwBuffer = (DWORD*)buffer; + + if (maxCpuId < 1) + goto qExit; + + if (dwBuffer[1] == 'uneG') { + if (dwBuffer[3] == 'Ieni') { + if (dwBuffer[2] == 'letn') { // get SMT/multicore enumeration for Intel EM64T + + + // TODO: Currently GetLogicalCpuCountFromOS() and GetLogicalCpuCountFallback() are broken on + // multi-core processor, but we never call into those two functions since we don't halve the + // gen0size when it's prescott and above processor. We keep the old version here for earlier + // generation system(Northwood based), perf data suggests on those systems, halve gen0 size + // still boost the performance(ex:Biztalk boosts about 17%). So on earlier systems(Northwood) + // based, we still go ahead and halve gen0 size. The logic in GetLogicalCpuCountFromOS() + // and GetLogicalCpuCountFallback() works fine for those earlier generation systems. + // If it's a Prescott and above processor or Multi-core, perf data suggests not to halve gen0 + // size at all gives us overall better performance. + // This is going to be fixed with a new version in orcas time frame. + + if( (maxCpuId > 3) && (maxCpuId < 0x80000000) ) + goto qExit; + + val = GetLogicalCpuCountFromOS(); //try to obtain HT enumeration from OS API + if (val ) + { + pParam->retVal = val; // OS API HT enumeration successful, we are Done + goto qExit; + } + + val = GetLogicalCpuCountFallback(); // Fallback to HT enumeration using CPUID + if( val ) + pParam->retVal = val; + } + } + } +qExit: ; + } + + PAL_EXCEPT_FILTER(DefaultCatchFilter) + { + } + PAL_ENDTRY + + if (val == 0) + { + val = param.retVal; + } + + return param.retVal; +} + +void EncodeLoadAndJumpThunk (LPBYTE pBuffer, LPVOID pv, LPVOID pTarget) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + + PRECONDITION(CheckPointer(pBuffer)); + } + CONTRACTL_END; + + // mov r10, pv 49 ba xx xx xx xx xx xx xx xx + + pBuffer[0] = 0x49; + pBuffer[1] = 0xBA; + + *((UINT64 UNALIGNED *)&pBuffer[2]) = (UINT64)pv; + + // mov rax, pTarget 48 b8 xx xx xx xx xx xx xx xx + + pBuffer[10] = 0x48; + pBuffer[11] = 0xB8; + + *((UINT64 UNALIGNED *)&pBuffer[12]) = (UINT64)pTarget; + + // jmp rax ff e0 + + pBuffer[20] = 0xFF; + pBuffer[21] = 0xE0; + + _ASSERTE(DbgIsExecutable(pBuffer, 22)); +} + +void emitCOMStubCall (ComCallMethodDesc *pCOMMethod, PCODE target) +{ + CONTRACT_VOID + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACT_END; + + BYTE *pBuffer = (BYTE*)pCOMMethod - COMMETHOD_CALL_PRESTUB_SIZE; + + // We need the target to be in a 64-bit aligned memory location and the call instruction + // to immediately precede the ComCallMethodDesc. We'll generate an indirect call to avoid + // consuming 3 qwords for this (mov rax, | target | nops & call rax). + + // dq 123456789abcdef0h + // nop 90 + // nop 90 + // call [$ - 10] ff 15 f0 ff ff ff + + *((UINT64 *)&pBuffer[COMMETHOD_CALL_PRESTUB_ADDRESS_OFFSET]) = (UINT64)target; + + pBuffer[-2] = 0x90; + pBuffer[-1] = 0x90; + + pBuffer[0] = 0xFF; + pBuffer[1] = 0x15; + *((UINT32 UNALIGNED *)&pBuffer[2]) = (UINT32)(COMMETHOD_CALL_PRESTUB_ADDRESS_OFFSET - COMMETHOD_CALL_PRESTUB_SIZE); + + _ASSERTE(DbgIsExecutable(pBuffer, COMMETHOD_CALL_PRESTUB_SIZE)); + + RETURN; +} + +void emitJump(LPBYTE pBuffer, LPVOID target) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + + PRECONDITION(CheckPointer(pBuffer)); + } + CONTRACTL_END; + + // mov rax, 123456789abcdef0h 48 b8 xx xx xx xx xx xx xx xx + // jmp rax ff e0 + + pBuffer[0] = 0x48; + pBuffer[1] = 0xB8; + + *((UINT64 UNALIGNED *)&pBuffer[2]) = (UINT64)target; + + pBuffer[10] = 0xFF; + pBuffer[11] = 0xE0; + + _ASSERTE(DbgIsExecutable(pBuffer, 12)); +} + +void UMEntryThunkCode::Encode(BYTE* pTargetCode, void* pvSecretParam) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // padding // CC CC CC CC + // mov r10, pUMEntryThunk // 49 ba xx xx xx xx xx xx xx xx // METHODDESC_REGISTER + // mov rax, pJmpDest // 48 b8 xx xx xx xx xx xx xx xx // need to ensure this imm64 is qword aligned + // TAILJMP_RAX // 48 FF E0 + +#ifdef _DEBUG + m_padding[0] = X86_INSTR_INT3; + m_padding[1] = X86_INSTR_INT3; + m_padding[2] = X86_INSTR_INT3; + m_padding[3] = X86_INSTR_INT3; +#endif // _DEBUG + m_movR10[0] = REX_PREFIX_BASE | REX_OPERAND_SIZE_64BIT | REX_OPCODE_REG_EXT; + m_movR10[1] = 0xBA; + m_uet = pvSecretParam; + m_movRAX[0] = REX_PREFIX_BASE | REX_OPERAND_SIZE_64BIT; + m_movRAX[1] = 0xB8; + m_execstub = pTargetCode; + m_jmpRAX[0] = REX_PREFIX_BASE | REX_OPERAND_SIZE_64BIT; + m_jmpRAX[1] = 0xFF; + m_jmpRAX[2] = 0xE0; + + _ASSERTE(DbgIsExecutable(&m_movR10[0], &m_jmpRAX[3]-&m_movR10[0])); +} + +UMEntryThunk* UMEntryThunk::Decode(LPVOID pCallback) +{ + LIMITED_METHOD_CONTRACT; + + UMEntryThunkCode *pThunkCode = (UMEntryThunkCode*)((BYTE*)pCallback - UMEntryThunkCode::GetEntryPointOffset()); + + return (UMEntryThunk*)pThunkCode->m_uet; +} + +INT32 rel32UsingJumpStub(INT32 UNALIGNED * pRel32, PCODE target, MethodDesc *pMethod, LoaderAllocator *pLoaderAllocator /* = NULL */) +{ + CONTRACTL + { + THROWS; // Creating a JumpStub could throw OutOfMemory + GC_NOTRIGGER; + + PRECONDITION(pMethod != NULL || pLoaderAllocator != NULL); + // If a loader allocator isn't explicitly provided, we must be able to get one via the MethodDesc. + PRECONDITION(pLoaderAllocator != NULL || pMethod->GetLoaderAllocator() != NULL); + // If a domain is provided, the MethodDesc mustn't yet be set up to have one, or it must match the MethodDesc's domain, + // unless we're in a compilation domain (NGen loads assemblies as domain-bound but compiles them as domain neutral). + PRECONDITION(!pLoaderAllocator || !pMethod || pMethod->GetMethodDescChunk()->GetMethodTablePtr()->IsNull() || + pLoaderAllocator == pMethod->GetMethodDescChunk()->GetFirstMethodDesc()->GetLoaderAllocatorForCode() || IsCompilationProcess()); + } + CONTRACTL_END; + + TADDR baseAddr = (TADDR)pRel32 + 4; + + INT_PTR offset = target - baseAddr; + + if (!FitsInI4(offset) INDEBUG(|| PEDecoder::GetForceRelocs())) + { + TADDR loAddr = baseAddr + INT32_MIN; + if (loAddr > baseAddr) loAddr = UINT64_MIN; // overflow + + TADDR hiAddr = baseAddr + INT32_MAX; + if (hiAddr < baseAddr) hiAddr = UINT64_MAX; // overflow + + PCODE jumpStubAddr = ExecutionManager::jumpStub(pMethod, + target, + (BYTE *)loAddr, + (BYTE *)hiAddr, + pLoaderAllocator); + + offset = jumpStubAddr - baseAddr; + + if (!FitsInI4(offset)) + { + _ASSERTE(!"jump stub was not in expected range"); + EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); + } + } + + _ASSERTE(FitsInI4(offset)); + return static_cast(offset); +} + +BOOL DoesSlotCallPrestub(PCODE pCode) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + PRECONDITION(pCode != GetPreStubEntryPoint()); + } CONTRACTL_END; + + // AMD64 has the following possible sequences for prestub logic: + // 1. slot -> temporary entrypoint -> prestub + // 2. slot -> precode -> prestub + // 3. slot -> precode -> jumprel64 (jump stub) -> prestub + // 4. slot -> precode -> jumprel64 (NGEN case) -> prestub + +#ifdef HAS_COMPACT_ENTRYPOINTS + if (MethodDescChunk::GetMethodDescFromCompactEntryPoint(pCode, TRUE) != NULL) + { + return TRUE; + } +#endif + + if (!IS_ALIGNED(pCode, PRECODE_ALIGNMENT)) + { + return FALSE; + } + +#ifdef HAS_FIXUP_PRECODE + if (*PTR_BYTE(pCode) == X86_INSTR_CALL_REL32) + { + // Note that call could have been patched to jmp in the meantime + pCode = rel32Decode(pCode+1); + +#ifdef FEATURE_PREJIT + // NGEN helper + if (*PTR_BYTE(pCode) == X86_INSTR_JMP_REL32) { + pCode = (TADDR)rel32Decode(pCode+1); + } +#endif + + // JumpStub + if (isJumpRel64(pCode)) { + pCode = decodeJump64(pCode); + } + + return pCode == (TADDR)PrecodeFixupThunk; + } +#endif + + if (*PTR_USHORT(pCode) != X86_INSTR_MOV_R10_IMM64 || // mov rax,XXXX + *PTR_BYTE(pCode+10) != X86_INSTR_NOP || // nop + *PTR_BYTE(pCode+11) != X86_INSTR_JMP_REL32) // jmp rel32 + { + return FALSE; + } + pCode = rel32Decode(pCode+12); + +#ifdef FEATURE_PREJIT + // NGEN helper + if (*PTR_BYTE(pCode) == X86_INSTR_JMP_REL32) { + pCode = (TADDR)rel32Decode(pCode+1); + } +#endif + + // JumpStub + if (isJumpRel64(pCode)) { + pCode = decodeJump64(pCode); + } + + return pCode == GetPreStubEntryPoint(); +} + +// +// Some AMD64 assembly functions have one or more DWORDS at the end of the function +// that specify the offsets where significant instructions are +// we use this function to get at these offsets +// +DWORD GetOffsetAtEndOfFunction(ULONGLONG uImageBase, + PT_RUNTIME_FUNCTION pFunctionEntry, + int offsetNum /* = 1*/) +{ + CONTRACTL + { + MODE_ANY; + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + PRECONDITION((offsetNum > 0) && (offsetNum < 20)); /* we only allow reasonable offsetNums 1..19 */ + } + CONTRACTL_END; + + DWORD functionSize = pFunctionEntry->EndAddress - pFunctionEntry->BeginAddress; + BYTE* pEndOfFunction = (BYTE*) (uImageBase + pFunctionEntry->EndAddress); + DWORD* pOffset = (DWORD*) (pEndOfFunction) - offsetNum; + DWORD offsetInFunc = *pOffset; + + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/cGenAMD64.cpp", (offsetInFunc >= 0) && (offsetInFunc < functionSize)); + + return offsetInFunc; +} + +//========================================================================================== +// In NGen image, virtual slots inherited from cross-module dependencies point to jump thunks. +// These jump thunk initially point to VirtualMethodFixupStub which transfers control here. +// This method 'VirtualMethodFixupWorker' will patch the jump thunk to point to the actual +// inherited method body after we have execute the precode and a stable entry point. +// +EXTERN_C PCODE VirtualMethodFixupWorker(TransitionBlock * pTransitionBlock, CORCOMPILE_VIRTUAL_IMPORT_THUNK * pThunk) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; // GC not allowed until we call pEMFrame->SetFunction(pMD); + + ENTRY_POINT; + } + CONTRACTL_END; + + MAKE_CURRENT_THREAD_AVAILABLE(); + + PCODE pCode = NULL; + MethodDesc * pMD = NULL; + +#ifdef _DEBUG + Thread::ObjectRefFlush(CURRENT_THREAD); +#endif + + BEGIN_SO_INTOLERANT_CODE(CURRENT_THREAD); + + _ASSERTE(IS_ALIGNED((size_t)pThunk, sizeof(INT64))); + + FrameWithCookie frame(pTransitionBlock); + ExternalMethodFrame * pEMFrame = &frame; + + OBJECTREF pThisPtr = pEMFrame->GetThis(); + _ASSERTE(pThisPtr != NULL); + VALIDATEOBJECT(pThisPtr); + + MethodTable * pMT = pThisPtr->GetTrueMethodTable(); + + WORD slotNumber = pThunk->slotNum; + _ASSERTE(slotNumber != (WORD)-1); + + pCode = pMT->GetRestoredSlot(slotNumber); + + if (!DoesSlotCallPrestub(pCode)) + { + pMD = MethodTable::GetMethodDescForSlotAddress(pCode); + + pEMFrame->SetFunction(pMD); // We will use the pMD to enumerate the GC refs in the arguments + pEMFrame->Push(CURRENT_THREAD); + + INSTALL_MANAGED_EXCEPTION_DISPATCHER; + INSTALL_UNWIND_AND_CONTINUE_HANDLER_NO_PROBE; + + // Skip fixup precode jump for better perf + PCODE pDirectTarget = Precode::TryToSkipFixupPrecode(pCode); + if (pDirectTarget != NULL) + pCode = pDirectTarget; + + INT64 oldValue = *(INT64*)pThunk; + BYTE* pOldValue = (BYTE*)&oldValue; + + if (pOldValue[0] == X86_INSTR_CALL_REL32) + { + INT64 newValue = oldValue; + BYTE* pNewValue = (BYTE*)&newValue; + pNewValue[0] = X86_INSTR_JMP_REL32; + + *(INT32 *)(pNewValue+1) = rel32UsingJumpStub((INT32*)(&pThunk->callJmp[1]), pCode, pMD, NULL); + + _ASSERTE(IS_ALIGNED(pThunk, sizeof(INT64))); + EnsureWritableExecutablePages(pThunk, sizeof(INT64)); + FastInterlockCompareExchangeLong((INT64*)pThunk, newValue, oldValue); + + FlushInstructionCache(GetCurrentProcess(), pThunk, 8); + } + + UNINSTALL_UNWIND_AND_CONTINUE_HANDLER_NO_PROBE; + UNINSTALL_MANAGED_EXCEPTION_DISPATCHER; + pEMFrame->Pop(CURRENT_THREAD); + } + + // Ready to return + + END_SO_INTOLERANT_CODE; + + return pCode; +} + +#ifdef FEATURE_READYTORUN + +// +// Allocation of dynamic helpers +// + +#define DYNAMIC_HELPER_ALIGNMENT sizeof(TADDR) + +#define BEGIN_DYNAMIC_HELPER_EMIT(size) \ + SIZE_T cb = size; \ + SIZE_T cbAligned = ALIGN_UP(cb, DYNAMIC_HELPER_ALIGNMENT); \ + BYTE * pStart = (BYTE *)(void *)pAllocator->GetDynamicHelpersHeap()->AllocAlignedMem(cbAligned, DYNAMIC_HELPER_ALIGNMENT); \ + BYTE * p = pStart; + +#define END_DYNAMIC_HELPER_EMIT() \ + _ASSERTE(pStart + cb == p); \ + while (p < pStart + cbAligned) *p++ = X86_INSTR_INT3; \ + ClrFlushInstructionCache(pStart, cbAligned); \ + return (PCODE)pStart + +PCODE DynamicHelpers::CreateHelper(LoaderAllocator * pAllocator, TADDR arg, PCODE target) +{ + STANDARD_VM_CONTRACT; + + BEGIN_DYNAMIC_HELPER_EMIT(15); + +#ifdef UNIX_AMD64_ABI + *(UINT16 *)p = 0xBF48; // mov rdi, XXXXXX +#else + *(UINT16 *)p = 0xB948; // mov rcx, XXXXXX +#endif + p += 2; + *(TADDR *)p = arg; + p += 8; + + *p++ = X86_INSTR_JMP_REL32; // jmp rel32 + *(INT32 *)p = rel32UsingJumpStub((INT32 *)p, target, NULL, pAllocator); + p += 4; + + END_DYNAMIC_HELPER_EMIT(); +} + +void DynamicHelpers::EmitHelperWithArg(BYTE*& p, LoaderAllocator * pAllocator, TADDR arg, PCODE target) +{ + CONTRACTL + { + GC_NOTRIGGER; + PRECONDITION(p != NULL && target != NULL); + } + CONTRACTL_END; + + // Move an an argument into the second argument register and jump to a target function. + +#ifdef UNIX_AMD64_ABI + *(UINT16 *)p = 0xBE48; // mov rsi, XXXXXX +#else + *(UINT16 *)p = 0xBA48; // mov rdx, XXXXXX +#endif + p += 2; + *(TADDR *)p = arg; + p += 8; + + *p++ = X86_INSTR_JMP_REL32; // jmp rel32 + *(INT32 *)p = rel32UsingJumpStub((INT32 *)p, target, NULL, pAllocator); + p += 4; +} + +PCODE DynamicHelpers::CreateHelperWithArg(LoaderAllocator * pAllocator, TADDR arg, PCODE target) +{ + BEGIN_DYNAMIC_HELPER_EMIT(15); + + EmitHelperWithArg(p, pAllocator, arg, target); + + END_DYNAMIC_HELPER_EMIT(); +} + +PCODE DynamicHelpers::CreateHelper(LoaderAllocator * pAllocator, TADDR arg, TADDR arg2, PCODE target) +{ + BEGIN_DYNAMIC_HELPER_EMIT(25); + +#ifdef UNIX_AMD64_ABI + *(UINT16 *)p = 0xBF48; // mov rdi, XXXXXX +#else + *(UINT16 *)p = 0xB948; // mov rcx, XXXXXX +#endif + p += 2; + *(TADDR *)p = arg; + p += 8; + +#ifdef UNIX_AMD64_ABI + *(UINT16 *)p = 0xBE48; // mov rsi, XXXXXX +#else + *(UINT16 *)p = 0xBA48; // mov rdx, XXXXXX +#endif + p += 2; + *(TADDR *)p = arg2; + p += 8; + + *p++ = X86_INSTR_JMP_REL32; // jmp rel32 + *(INT32 *)p = rel32UsingJumpStub((INT32 *)p, target, NULL, pAllocator); + p += 4; + + END_DYNAMIC_HELPER_EMIT(); +} + +PCODE DynamicHelpers::CreateHelperArgMove(LoaderAllocator * pAllocator, TADDR arg, PCODE target) +{ + BEGIN_DYNAMIC_HELPER_EMIT(18); + +#ifdef UNIX_AMD64_ABI + *p++ = 0x48; // mov rsi, rdi + *(UINT16 *)p = 0xF78B; +#else + *p++ = 0x48; // mov rdx, rcx + *(UINT16 *)p = 0xD18B; +#endif + p += 2; + +#ifdef UNIX_AMD64_ABI + *(UINT16 *)p = 0xBF48; // mov rdi, XXXXXX +#else + *(UINT16 *)p = 0xB948; // mov rcx, XXXXXX +#endif + p += 2; + *(TADDR *)p = arg; + p += 8; + + *p++ = X86_INSTR_JMP_REL32; // jmp rel32 + *(INT32 *)p = rel32UsingJumpStub((INT32 *)p, target, NULL, pAllocator); + p += 4; + + END_DYNAMIC_HELPER_EMIT(); +} + +PCODE DynamicHelpers::CreateReturn(LoaderAllocator * pAllocator) +{ + BEGIN_DYNAMIC_HELPER_EMIT(1); + + *p++ = 0xC3; // ret + + END_DYNAMIC_HELPER_EMIT(); +} + +PCODE DynamicHelpers::CreateReturnConst(LoaderAllocator * pAllocator, TADDR arg) +{ + BEGIN_DYNAMIC_HELPER_EMIT(11); + + *(UINT16 *)p = 0xB848; // mov rax, XXXXXX + p += 2; + *(TADDR *)p = arg; + p += 8; + + *p++ = 0xC3; // ret + + END_DYNAMIC_HELPER_EMIT(); +} + +PCODE DynamicHelpers::CreateReturnIndirConst(LoaderAllocator * pAllocator, TADDR arg, INT8 offset) +{ + BEGIN_DYNAMIC_HELPER_EMIT((offset != 0) ? 15 : 11); + + *(UINT16 *)p = 0xA148; // mov rax, [XXXXXX] + p += 2; + *(TADDR *)p = arg; + p += 8; + + if (offset != 0) + { + // add rax, + *p++ = 0x48; + *p++ = 0x83; + *p++ = 0xC0; + *p++ = offset; + } + + *p++ = 0xC3; // ret + + END_DYNAMIC_HELPER_EMIT(); +} + +PCODE DynamicHelpers::CreateHelperWithTwoArgs(LoaderAllocator * pAllocator, TADDR arg, PCODE target) +{ + BEGIN_DYNAMIC_HELPER_EMIT(15); + +#ifdef UNIX_AMD64_ABI + *(UINT16 *)p = 0xBA48; // mov rdx, XXXXXX +#else + *(UINT16 *)p = 0xB849; // mov r8, XXXXXX +#endif + p += 2; + *(TADDR *)p = arg; + p += 8; + + *p++ = X86_INSTR_JMP_REL32; // jmp rel32 + *(INT32 *)p = rel32UsingJumpStub((INT32 *)p, target, NULL, pAllocator); + p += 4; + + END_DYNAMIC_HELPER_EMIT(); +} + +PCODE DynamicHelpers::CreateHelperWithTwoArgs(LoaderAllocator * pAllocator, TADDR arg, TADDR arg2, PCODE target) +{ + BEGIN_DYNAMIC_HELPER_EMIT(25); + +#ifdef UNIX_AMD64_ABI + *(UINT16 *)p = 0xBA48; // mov rdx, XXXXXX +#else + *(UINT16 *)p = 0xB849; // mov r8, XXXXXX +#endif + p += 2; + *(TADDR *)p = arg; + p += 8; + +#ifdef UNIX_AMD64_ABI + *(UINT16 *)p = 0xB948; // mov rcx, XXXXXX +#else + *(UINT16 *)p = 0xB949; // mov r9, XXXXXX +#endif + p += 2; + *(TADDR *)p = arg2; + p += 8; + + *p++ = X86_INSTR_JMP_REL32; // jmp rel32 + *(INT32 *)p = rel32UsingJumpStub((INT32 *)p, target, NULL, pAllocator); + p += 4; + + END_DYNAMIC_HELPER_EMIT(); +} + +PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator, CORINFO_RUNTIME_LOOKUP * pLookup, DWORD dictionaryIndexAndSlot, Module * pModule) +{ + STANDARD_VM_CONTRACT; + + PCODE helperAddress = (pLookup->helper == CORINFO_HELP_RUNTIMEHANDLE_METHOD ? + GetEEFuncEntryPoint(JIT_GenericHandleMethodWithSlotAndModule) : + GetEEFuncEntryPoint(JIT_GenericHandleClassWithSlotAndModule)); + + GenericHandleArgs * pArgs = (GenericHandleArgs *)(void *)pAllocator->GetDynamicHelpersHeap()->AllocAlignedMem(sizeof(GenericHandleArgs), DYNAMIC_HELPER_ALIGNMENT); + pArgs->dictionaryIndexAndSlot = dictionaryIndexAndSlot; + pArgs->signature = pLookup->signature; + pArgs->module = (CORINFO_MODULE_HANDLE)pModule; + + // It's available only via the run-time helper function + if (pLookup->indirections == CORINFO_USEHELPER) + { + BEGIN_DYNAMIC_HELPER_EMIT(15); + + // rcx/rdi contains the generic context parameter + // mov rdx/rsi,pArgs + // jmp helperAddress + EmitHelperWithArg(p, pAllocator, (TADDR)pArgs, helperAddress); + + END_DYNAMIC_HELPER_EMIT(); + } + else + { + int indirectionsSize = 0; + for (WORD i = 0; i < pLookup->indirections; i++) + indirectionsSize += (pLookup->offsets[i] >= 0x80 ? 7 : 4); + + int codeSize = indirectionsSize + (pLookup->testForNull ? 30 : 4); + + BEGIN_DYNAMIC_HELPER_EMIT(codeSize); + + if (pLookup->testForNull) + { + // rcx/rdi contains the generic context parameter. Save a copy of it in the rax register +#ifdef UNIX_AMD64_ABI + *(UINT32*)p = 0x00f88948; p += 3; // mov rax,rdi +#else + *(UINT32*)p = 0x00c88948; p += 3; // mov rax,rcx +#endif + } + + for (WORD i = 0; i < pLookup->indirections; i++) + { +#ifdef UNIX_AMD64_ABI + // mov rdi,qword ptr [rdi+offset] + if (pLookup->offsets[i] >= 0x80) + { + *(UINT32*)p = 0x00bf8b48; p += 3; + *(UINT32*)p = (UINT32)pLookup->offsets[i]; p += 4; + } + else + { + *(UINT32*)p = 0x007f8b48; p += 3; + *p++ = (BYTE)pLookup->offsets[i]; + } +#else + // mov rcx,qword ptr [rcx+offset] + if (pLookup->offsets[i] >= 0x80) + { + *(UINT32*)p = 0x00898b48; p += 3; + *(UINT32*)p = (UINT32)pLookup->offsets[i]; p += 4; + } + else + { + *(UINT32*)p = 0x00498b48; p += 3; + *p++ = (BYTE)pLookup->offsets[i]; + } +#endif + } + + // No null test required + if (!pLookup->testForNull) + { + // No fixups needed for R2R + +#ifdef UNIX_AMD64_ABI + *(UINT32*)p = 0x00f88948; p += 3; // mov rax,rdi +#else + *(UINT32*)p = 0x00c88948; p += 3; // mov rax,rcx +#endif + *p++ = 0xC3; // ret + } + else + { + // rcx/rdi contains the value of the dictionary slot entry + + _ASSERTE(pLookup->indirections != 0); + +#ifdef UNIX_AMD64_ABI + *(UINT32*)p = 0x00ff8548; p += 3; // test rdi,rdi +#else + *(UINT32*)p = 0x00c98548; p += 3; // test rcx,rcx +#endif + + // je 'HELPER_CALL' (a jump of 4 bytes) + *(UINT16*)p = 0x0474; p += 2; + +#ifdef UNIX_AMD64_ABI + *(UINT32*)p = 0x00f88948; p += 3; // mov rax,rdi +#else + *(UINT32*)p = 0x00c88948; p += 3; // mov rax,rcx +#endif + *p++ = 0xC3; // ret + + // 'HELPER_CALL' + { + // Put the generic context back into rcx (was previously saved in rax) +#ifdef UNIX_AMD64_ABI + *(UINT32*)p = 0x00c78948; p += 3; // mov rdi,rax +#else + *(UINT32*)p = 0x00c18948; p += 3; // mov rcx,rax +#endif + + // mov rdx,pArgs + // jmp helperAddress + EmitHelperWithArg(p, pAllocator, (TADDR)pArgs, helperAddress); + } + } + + END_DYNAMIC_HELPER_EMIT(); + } +} + +#endif // FEATURE_READYTORUN + +#endif // DACCESS_COMPILE diff --git a/src/vm/amd64/cgencpu.h b/src/vm/amd64/cgencpu.h new file mode 100644 index 0000000000..258ac38915 --- /dev/null +++ b/src/vm/amd64/cgencpu.h @@ -0,0 +1,562 @@ +// 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. +// CGENCPU.H - +// +// Various helper routines for generating AMD64 assembly code. +// +// DO NOT INCLUDE THIS FILE DIRECTLY - ALWAYS USE CGENSYS.H INSTEAD +// + + + +#ifndef _TARGET_AMD64_ +#error Should only include "AMD64\cgencpu.h" for AMD64 builds +#endif + +#ifndef __cgencpu_h__ +#define __cgencpu_h__ + +#include "xmmintrin.h" + +// Given a return address retrieved during stackwalk, +// this is the offset by which it should be decremented to lend somewhere in a call instruction. +#define STACKWALK_CONTROLPC_ADJUST_OFFSET 1 + +// preferred alignment for data +#define DATA_ALIGNMENT 8 + +class MethodDesc; +class FramedMethodFrame; +class Module; +struct VASigCookie; +class ComCallMethodDesc; + +// +// functions implemented in AMD64 assembly +// +EXTERN_C void InstantiatingMethodStubWorker(void); +EXTERN_C void SinglecastDelegateInvokeStub(); +EXTERN_C void FastCallFinalizeWorker(Object *obj, PCODE funcPtr); + +#define COMMETHOD_PREPAD 16 // # extra bytes to allocate in addition to sizeof(ComCallMethodDesc) +#define COMMETHOD_CALL_PRESTUB_SIZE 6 // 32-bit indirect relative call +#define COMMETHOD_CALL_PRESTUB_ADDRESS_OFFSET -10 // the offset of the call target address inside the prestub + +#define STACK_ALIGN_SIZE 16 + +#define JUMP_ALLOCATE_SIZE 12 // # bytes to allocate for a 64-bit jump instruction +#define BACK_TO_BACK_JUMP_ALLOCATE_SIZE 12 // # bytes to allocate for a back to back 64-bit jump instruction +#define SIZEOF_LOAD_AND_JUMP_THUNK 22 // # bytes to mov r10, X; jmp Z +#define SIZEOF_LOAD2_AND_JUMP_THUNK 32 // # bytes to mov r10, X; mov r11, Y; jmp Z + +// Also in Zapper.h, CorCompile.h, FnTableAccess.h +#define USE_INDIRECT_CODEHEADER // use CodeHeader, RealCodeHeader construct + +#define HAS_NDIRECT_IMPORT_PRECODE 1 +//#define HAS_REMOTING_PRECODE 1 // TODO: Implement +#define HAS_FIXUP_PRECODE 1 +#define HAS_FIXUP_PRECODE_CHUNKS 1 + +// ThisPtrRetBufPrecode one is necessary for closed delegates over static methods with return buffer +#define HAS_THISPTR_RETBUF_PRECODE 1 + +#define CODE_SIZE_ALIGN 16 // must alloc code blocks on 8-byte boundaries; for perf reasons we use 16 byte boundaries +#define CACHE_LINE_SIZE 64 // Current AMD64 processors have 64-byte cache lines as per AMD64 optmization manual +#define LOG2SLOT LOG2_PTRSIZE + +#define ENREGISTERED_RETURNTYPE_INTEGER_MAXSIZE 8 // bytes +#define ENREGISTERED_PARAMTYPE_MAXSIZE 8 // bytes + +#ifdef UNIX_AMD64_ABI +#define ENREGISTERED_RETURNTYPE_MAXSIZE 16 // bytes +#define CALLDESCR_ARGREGS 1 // CallDescrWorker has ArgumentRegister parameter +#define CALLDESCR_FPARGREGS 1 // CallDescrWorker has FloatArgumentRegisters parameter +#else +#define ENREGISTERED_RETURNTYPE_MAXSIZE 8 // bytes +#define COM_STUBS_SEPARATE_FP_LOCATIONS +#define CALLDESCR_REGTYPEMAP 1 +#endif + +#define INSTRFMT_K64SMALL +#define INSTRFMT_K64 + +#ifndef FEATURE_PAL +#define USE_REDIRECT_FOR_GCSTRESS +#endif // FEATURE_PAL + +// +// REX prefix byte +// +#define REX_PREFIX_BASE 0x40 // 0100xxxx +#define REX_OPERAND_SIZE_64BIT 0x08 // xxxx1xxx +#define REX_MODRM_REG_EXT 0x04 // xxxxx1xx // use for 'middle' 3 bit field of mod/r/m +#define REX_SIB_INDEX_EXT 0x02 // xxxxxx10 +#define REX_MODRM_RM_EXT 0x01 // XXXXXXX1 // use for low 3 bit field of mod/r/m +#define REX_SIB_BASE_EXT 0x01 // XXXXXXX1 +#define REX_OPCODE_REG_EXT 0x01 // XXXXXXX1 + +#define X86_REGISTER_MASK 0x7 + +#define X86RegFromAMD64Reg(extended_reg) \ + ((X86Reg)(((int)extended_reg) & X86_REGISTER_MASK)) + +// Max size of optimized TLS helpers +#ifdef _DEBUG +// Debug build needs extra space for last error trashing +#define TLS_GETTER_MAX_SIZE 0x30 +#else +#define TLS_GETTER_MAX_SIZE 0x18 +#endif + +//======================================================================= +// IMPORTANT: This value is used to figure out how much to allocate +// for a fixed array of FieldMarshaler's. That means it must be at least +// as large as the largest FieldMarshaler subclass. This requirement +// is guarded by an assert. +//======================================================================= +#define MAXFIELDMARSHALERSIZE 40 + + +// Why is the return value ARG_SLOT? On 64-bit systems, that is 64-bits +// and much bigger than necessary for R4, requiring explicit downcasts. +inline +ARG_SLOT FPSpillToR4(void* pSpillSlot) +{ + LIMITED_METHOD_CONTRACT; + return *(DWORD*)pSpillSlot; +} + +inline +ARG_SLOT FPSpillToR8(void* pSpillSlot) +{ + LIMITED_METHOD_CONTRACT; + return *(SIZE_T*)pSpillSlot; +} + +inline +void R4ToFPSpill(void* pSpillSlot, DWORD srcFloatAsDWORD) +{ + LIMITED_METHOD_CONTRACT; + *(SIZE_T*)pSpillSlot = (SIZE_T)srcFloatAsDWORD; + *((SIZE_T*)pSpillSlot + 1) = 0; +} + +inline +void R8ToFPSpill(void* pSpillSlot, SIZE_T srcDoubleAsSIZE_T) +{ + LIMITED_METHOD_CONTRACT; + *(SIZE_T*)pSpillSlot = srcDoubleAsSIZE_T; + *((SIZE_T*)pSpillSlot + 1) = 0; +} + + +#ifdef CROSSGEN_COMPILE +#define GetEEFuncEntryPoint(pfn) 0x1001 +#else +#define GetEEFuncEntryPoint(pfn) GFN_TADDR(pfn) +#endif + + +//********************************************************************** +// Parameter size +//********************************************************************** + +typedef INT64 StackElemType; +#define STACK_ELEM_SIZE sizeof(StackElemType) + +// !! This expression assumes STACK_ELEM_SIZE is a power of 2. +#define StackElemSize(parmSize) (((parmSize) + STACK_ELEM_SIZE - 1) & ~((ULONG)(STACK_ELEM_SIZE - 1))) + +//********************************************************************** +// Frames +//********************************************************************** +//-------------------------------------------------------------------- +// This represents some of the TransitionFrame fields that are +// stored at negative offsets. +//-------------------------------------------------------------------- +struct REGDISPLAY; + +//-------------------------------------------------------------------- +// This represents the arguments that are stored in volatile registers. +// This should not overlap the CalleeSavedRegisters since those are already +// saved separately and it would be wasteful to save the same register twice. +// If we do use a non-volatile register as an argument, then the ArgIterator +// will probably have to communicate this back to the PromoteCallerStack +// routine to avoid a double promotion. +//-------------------------------------------------------------------- +#ifdef UNIX_AMD64_ABI + +#define ENUM_ARGUMENT_REGISTERS() \ + ARGUMENT_REGISTER(RDI) \ + ARGUMENT_REGISTER(RSI) \ + ARGUMENT_REGISTER(RDX) \ + ARGUMENT_REGISTER(RCX) \ + ARGUMENT_REGISTER(R8) \ + ARGUMENT_REGISTER(R9) + +#define NUM_ARGUMENT_REGISTERS 6 + +// The order of registers in this macro is hardcoded in assembly code +// at number of places +#define ENUM_CALLEE_SAVED_REGISTERS() \ + CALLEE_SAVED_REGISTER(R12) \ + CALLEE_SAVED_REGISTER(R13) \ + CALLEE_SAVED_REGISTER(R14) \ + CALLEE_SAVED_REGISTER(R15) \ + CALLEE_SAVED_REGISTER(Rbx) \ + CALLEE_SAVED_REGISTER(Rbp) + +#define NUM_CALLEE_SAVED_REGISTERS 6 + +#else // UNIX_AMD64_ABI + +#define ENUM_ARGUMENT_REGISTERS() \ + ARGUMENT_REGISTER(RCX) \ + ARGUMENT_REGISTER(RDX) \ + ARGUMENT_REGISTER(R8) \ + ARGUMENT_REGISTER(R9) + +#define NUM_ARGUMENT_REGISTERS 4 + +// The order of registers in this macro is hardcoded in assembly code +// at number of places +#define ENUM_CALLEE_SAVED_REGISTERS() \ + CALLEE_SAVED_REGISTER(Rdi) \ + CALLEE_SAVED_REGISTER(Rsi) \ + CALLEE_SAVED_REGISTER(Rbx) \ + CALLEE_SAVED_REGISTER(Rbp) \ + CALLEE_SAVED_REGISTER(R12) \ + CALLEE_SAVED_REGISTER(R13) \ + CALLEE_SAVED_REGISTER(R14) \ + CALLEE_SAVED_REGISTER(R15) + +#define NUM_CALLEE_SAVED_REGISTERS 8 + +#endif // UNIX_AMD64_ABI + +typedef DPTR(struct ArgumentRegisters) PTR_ArgumentRegisters; +struct ArgumentRegisters { + #define ARGUMENT_REGISTER(regname) INT_PTR regname; + ENUM_ARGUMENT_REGISTERS(); + #undef ARGUMENT_REGISTER +}; + +typedef DPTR(struct CalleeSavedRegisters) PTR_CalleeSavedRegisters; +struct CalleeSavedRegisters { + #define CALLEE_SAVED_REGISTER(regname) INT_PTR regname; + ENUM_CALLEE_SAVED_REGISTERS(); + #undef CALLEE_SAVED_REGISTER +}; + +struct CalleeSavedRegistersPointers { + #define CALLEE_SAVED_REGISTER(regname) PTR_TADDR p##regname; + ENUM_CALLEE_SAVED_REGISTERS(); + #undef CALLEE_SAVED_REGISTER +}; + +#define SCRATCH_REGISTER_X86REG kRAX + +#ifdef UNIX_AMD64_ABI +#define THIS_REG RDI +#define THIS_kREG kRDI +#else +#define THIS_REG RCX +#define THIS_kREG kRCX +#endif + +#ifdef UNIX_AMD64_ABI + +#define NUM_FLOAT_ARGUMENT_REGISTERS 8 + +typedef DPTR(struct FloatArgumentRegisters) PTR_FloatArgumentRegisters; +struct FloatArgumentRegisters { + M128A d[NUM_FLOAT_ARGUMENT_REGISTERS]; // xmm0-xmm7 +}; + +#endif + + +void UpdateRegDisplayFromCalleeSavedRegisters(REGDISPLAY * pRD, CalleeSavedRegisters * pRegs); + + +// Sufficient context for Try/Catch restoration. +struct EHContext { + // Not used +}; + +#define ARGUMENTREGISTERS_SIZE sizeof(ArgumentRegisters) + + +#include "stublinkeramd64.h" + + + +//********************************************************************** +// Exception handling +//********************************************************************** + +inline PCODE GetIP(const CONTEXT * context) +{ + CONTRACTL + { + SO_TOLERANT; + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + + PRECONDITION(CheckPointer(context)); + } + CONTRACTL_END; + + return PCODE(context->Rip); +} + +inline void SetIP(CONTEXT* context, PCODE rip) +{ + CONTRACTL + { + SO_TOLERANT; + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + + PRECONDITION(CheckPointer(context)); + } + CONTRACTL_END; + + context->Rip = (DWORD64) rip; +} + +inline TADDR GetSP(const CONTEXT * context) +{ + CONTRACTL + { + SO_TOLERANT; + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + + PRECONDITION(CheckPointer(context)); + } + CONTRACTL_END; + + return (TADDR)context->Rsp; +} +inline void SetSP(CONTEXT *context, TADDR rsp) +{ + CONTRACTL + { + SO_TOLERANT; + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + + PRECONDITION(CheckPointer(context)); + } + CONTRACTL_END; + + context->Rsp = rsp; +} + +#define SetFP(context, ebp) +inline TADDR GetFP(const CONTEXT * context) +{ + LIMITED_METHOD_CONTRACT; + + return (TADDR)(context->Rbp); +} + +extern "C" TADDR GetCurrentSP(); + +// Emits: +// mov r10, pv1 +// mov rax, pTarget +// jmp rax +void EncodeLoadAndJumpThunk (LPBYTE pBuffer, LPVOID pv, LPVOID pTarget); + + +// Get Rel32 destination, emit jumpStub if necessary +INT32 rel32UsingJumpStub(INT32 UNALIGNED * pRel32, PCODE target, MethodDesc *pMethod, LoaderAllocator *pLoaderAllocator = NULL); + +void emitCOMStubCall (ComCallMethodDesc *pCOMMethod, PCODE target); + +void emitJump(LPBYTE pBuffer, LPVOID target); + +BOOL isJumpRel32(PCODE pCode); +PCODE decodeJump32(PCODE pCode); + +BOOL isJumpRel64(PCODE pCode); +PCODE decodeJump64(PCODE pCode); + +// +// On IA64 back to back jumps should be separated by a nop bundle to get +// the best performance from the hardware's branch prediction logic. +// For all other platforms back to back jumps don't require anything special +// That is why we have these two wrapper functions that call emitJump and decodeJump +// +inline void emitBackToBackJump(LPBYTE pBuffer, LPVOID target) +{ + WRAPPER_NO_CONTRACT; + + emitJump(pBuffer, target); +} + +inline BOOL isBackToBackJump(PCODE pCode) +{ + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + return isJumpRel32(pCode) || isJumpRel64(pCode); +} + +inline PCODE decodeBackToBackJump(PCODE pCode) +{ + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + if (isJumpRel32(pCode)) + return decodeJump32(pCode); + else + if (isJumpRel64(pCode)) + return decodeJump64(pCode); + else + return NULL; +} + +extern "C" void setFPReturn(int fpSize, INT64 retVal); +extern "C" void getFPReturn(int fpSize, INT64 *retval); + + +struct ComToManagedExRecord; // defined in cgencpu.cpp + +inline BOOL IsUnmanagedValueTypeReturnedByRef(UINT sizeofvaluetype) +{ + LIMITED_METHOD_CONTRACT; + + if (sizeofvaluetype > ENREGISTERED_RETURNTYPE_MAXSIZE) + { + return TRUE; + } + else + { + return FALSE; + } +} + +#include +struct DECLSPEC_ALIGN(8) UMEntryThunkCode +{ + // padding // CC CC CC CC + // mov r10, pUMEntryThunk // 49 ba xx xx xx xx xx xx xx xx // METHODDESC_REGISTER + // mov rax, pJmpDest // 48 b8 xx xx xx xx xx xx xx xx // need to ensure this imm64 is qword aligned + // TAILJMP_RAX // 48 FF E0 + + BYTE m_padding[4]; + BYTE m_movR10[2]; // MOV R10, + LPVOID m_uet; // pointer to start of this structure + BYTE m_movRAX[2]; // MOV RAX, + DECLSPEC_ALIGN(8) + const BYTE* m_execstub; // pointer to destination code // ensure this is qword aligned + BYTE m_jmpRAX[3]; // JMP RAX + BYTE m_padding2[5]; + + void Encode(BYTE* pTargetCode, void* pvSecretParam); + + LPCBYTE GetEntryPoint() const + { + LIMITED_METHOD_CONTRACT; + + return (LPCBYTE)&m_movR10; + } + + static int GetEntryPointOffset() + { + LIMITED_METHOD_CONTRACT; + + return offsetof(UMEntryThunkCode, m_movR10); + } +}; +#include + +struct HijackArgs +{ +#ifndef FEATURE_MULTIREG_RETURN + union + { + ULONG64 Rax; + ULONG64 ReturnValue[1]; + }; +#else // FEATURE_MULTIREG_RETURN + union + { + struct + { + ULONG64 Rax; + ULONG64 Rdx; + }; + ULONG64 ReturnValue[2]; + }; +#endif // PLATFORM_UNIX + CalleeSavedRegisters Regs; + union + { + ULONG64 Rip; + size_t ReturnAddress; + }; +}; + +#ifndef DACCESS_COMPILE + +DWORD GetOffsetAtEndOfFunction(ULONGLONG uImageBase, + PT_RUNTIME_FUNCTION pFunctionEntry, + int offsetNum = 1); + +#endif // DACCESS_COMPILE + +// ClrFlushInstructionCache is used when we want to call FlushInstructionCache +// for a specific architecture in the common code, but not for other architectures. +// We call ClrFlushInstructionCache whenever we create or modify code in the heap. +// Currently ClrFlushInstructionCache has no effect on AMD64 +// + +inline BOOL ClrFlushInstructionCache(LPCVOID pCodeAddr, size_t sizeOfCode) +{ + // FlushInstructionCache(GetCurrentProcess(), pCodeAddr, sizeOfCode); + MemoryBarrier(); + return TRUE; +} + +#ifndef FEATURE_IMPLICIT_TLS +// +// JIT HELPER ALIASING FOR PORTABILITY. +// +// Create alias for optimized implementations of helpers provided on this platform +// +#define JIT_MonEnter JIT_MonEnter +#define JIT_MonEnterWorker JIT_MonEnterWorker_InlineGetThread +#define JIT_MonReliableEnter JIT_MonEnterWorker +#define JIT_MonTryEnter JIT_MonTryEnter_InlineGetThread +#define JIT_MonExit JIT_MonExit +#define JIT_MonExitWorker JIT_MonExitWorker_InlineGetThread +#define JIT_MonEnterStatic JIT_MonEnterStatic_InlineGetThread +#define JIT_MonExitStatic JIT_MonExitStatic_InlineGetThread + +#define JIT_GetSharedGCStaticBase JIT_GetSharedGCStaticBase_InlineGetAppDomain +#define JIT_GetSharedNonGCStaticBase JIT_GetSharedNonGCStaticBase_InlineGetAppDomain +#define JIT_GetSharedGCStaticBaseNoCtor JIT_GetSharedGCStaticBaseNoCtor_InlineGetAppDomain +#define JIT_GetSharedNonGCStaticBaseNoCtor JIT_GetSharedNonGCStaticBaseNoCtor_InlineGetAppDomain + +#endif // FEATURE_IMPLICIT_TLS + +#ifndef FEATURE_PAL + +#define JIT_ChkCastClass JIT_ChkCastClass +#define JIT_ChkCastClassSpecial JIT_ChkCastClassSpecial +#define JIT_IsInstanceOfClass JIT_IsInstanceOfClass +#define JIT_ChkCastInterface JIT_ChkCastInterface +#define JIT_IsInstanceOfInterface JIT_IsInstanceOfInterface + +#endif // FEATURE_PAL + +#define JIT_Stelem_Ref JIT_Stelem_Ref + +#endif // __cgencpu_h__ diff --git a/src/vm/amd64/crthelpers.S b/src/vm/amd64/crthelpers.S new file mode 100644 index 0000000000..168359e192 --- /dev/null +++ b/src/vm/amd64/crthelpers.S @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +.intel_syntax noprefix +#include "unixasmmacros.inc" +#include "asmconstants.h" + +// JIT_MemSet/JIT_MemCpy +// +// It is IMPORANT that the exception handling code is able to find these guys +// on the stack, but on non-windows platforms we can just defer to the platform +// implementation. +// + +LEAF_ENTRY JIT_MemSet, _TEXT + test rdx, rdx + jz Exit_MemSet + + cmp byte ptr [rdi], 0 + + jmp C_PLTFUNC(memset) + +Exit_MemSet: + ret + +LEAF_END_MARKED JIT_MemSet, _TEXT + +LEAF_ENTRY JIT_MemCpy, _TEXT + test rdx, rdx + jz Exit_MemCpy + + cmp byte ptr [rdi], 0 + cmp byte ptr [rsi], 0 + + jmp C_PLTFUNC(memcpy) + +Exit_MemCpy: + ret + +LEAF_END_MARKED JIT_MemCpy, _TEXT diff --git a/src/vm/amd64/excepamd64.cpp b/src/vm/amd64/excepamd64.cpp new file mode 100644 index 0000000000..2fc553a987 --- /dev/null +++ b/src/vm/amd64/excepamd64.cpp @@ -0,0 +1,585 @@ +// 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. + +/* EXCEP.CPP + * + */ +// + +// + +#include "common.h" + +#include "frames.h" +#include "threads.h" +#include "excep.h" +#include "object.h" +#include "field.h" +#include "dbginterface.h" +#include "cgensys.h" +#include "comutilnative.h" +#include "sigformat.h" +#include "siginfo.hpp" +#include "gc.h" +#include "eedbginterfaceimpl.h" //so we can clearexception in COMPlusThrow +#include "perfcounters.h" +#include "asmconstants.h" + +#include "exceptionhandling.h" + + + +#if !defined(DACCESS_COMPILE) + +VOID ResetCurrentContext() +{ + LIMITED_METHOD_CONTRACT; +} + +LONG CLRNoCatchHandler(EXCEPTION_POINTERS* pExceptionInfo, PVOID pv) +{ + return EXCEPTION_CONTINUE_SEARCH; +} + +#endif // !DACCESS_COMPILE + +inline PTR_CONTEXT GetCONTEXTFromRedirectedStubStackFrameWorker(UINT_PTR establisherFrame) +{ + LIMITED_METHOD_DAC_CONTRACT; + + SIZE_T rbp = establisherFrame + REDIRECTSTUB_ESTABLISHER_OFFSET_RBP; + PTR_PTR_CONTEXT ppContext = dac_cast((TADDR)rbp + REDIRECTSTUB_RBP_OFFSET_CONTEXT); + return *ppContext; +} + +PTR_CONTEXT GetCONTEXTFromRedirectedStubStackFrame(DISPATCHER_CONTEXT * pDispatcherContext) +{ + LIMITED_METHOD_DAC_CONTRACT; + + return GetCONTEXTFromRedirectedStubStackFrameWorker(pDispatcherContext->EstablisherFrame); +} + +PTR_CONTEXT GetCONTEXTFromRedirectedStubStackFrame(CONTEXT * pContext) +{ + LIMITED_METHOD_DAC_CONTRACT; + + return GetCONTEXTFromRedirectedStubStackFrameWorker(pContext->Rbp); +} + +#if !defined(DACCESS_COMPILE) + +FaultingExceptionFrame *GetFrameFromRedirectedStubStackFrame (DISPATCHER_CONTEXT *pDispatcherContext) +{ + LIMITED_METHOD_CONTRACT; + + return (FaultingExceptionFrame*)(pDispatcherContext->EstablisherFrame + THROWSTUB_ESTABLISHER_OFFSET_FaultingExceptionFrame); +} + +#endif // !DACCESS_COMPILE + +#if !defined(DACCESS_COMPILE) + +#define AMD64_SIZE64_PREFIX 0x48 +#define AMD64_ADD_IMM8_OP 0x83 +#define AMD64_ADD_IMM32_OP 0x81 +#define AMD64_JMP_IMM8_OP 0xeb +#define AMD64_JMP_IMM32_OP 0xe9 +#define AMD64_JMP_IND_OP 0xff +#define AMD64_JMP_IND_RAX 0x20 +#define AMD64_LEA_OP 0x8d +#define AMD64_POP_OP 0x58 +#define AMD64_RET_OP 0xc3 +#define AMD64_RET_OP_2 0xc2 +#define AMD64_REP_PREFIX 0xf3 +#define AMD64_NOP 0x90 +#define AMD64_INT3 0xCC + +#define AMD64_IS_REX_PREFIX(x) (((x) & 0xf0) == 0x40) + +#define FAKE_PROLOG_SIZE 1 +#define FAKE_FUNCTION_CODE_SIZE 1 + +#ifdef DEBUGGING_SUPPORTED +// +// If there is an Int3 opcode at the Address then this tries to get the +// correct Opcode for the address from the managed patch table. If this is +// called on an address which doesn't currently have an Int3 then the current +// opcode is returned. If there is no managed patch in the patch table +// corresponding to this address then the current opcode (0xCC) at Address is +// is returned. If a 0xCC is returned from this function it indicates an +// unmanaged patch at the address. +// +// If there is a managed patch at the address HasManagedBreakpoint is set to true. +// +// If there is a 0xCC at the address before the call to GetPatchedOpcode and +// still a 0xCC when we return then this is considered an unmanaged patch and +// HasManagedBreakpoint is set to true. +// +UCHAR GetOpcodeFromManagedBPForAddress(ULONG64 Address, BOOL* HasManagedBreakpoint, BOOL* HasUnmanagedBreakpoint) +{ + // If we don't see a breakpoint then quickly return. + if (((UCHAR)*(BYTE*)Address) != AMD64_INT3) + { + return ((UCHAR)*(BYTE*)Address); + } + + UCHAR PatchedOpcode; + PatchedOpcode = (UCHAR)g_pDebugInterface->GetPatchedOpcode((CORDB_ADDRESS_TYPE*)(BYTE*)Address); + + // If a non Int3 opcode is returned from GetPatchedOpcode then + // this function has a managed breakpoint + if (PatchedOpcode != AMD64_INT3) + { + (*HasManagedBreakpoint) = TRUE; + } + else + { + (*HasUnmanagedBreakpoint) = TRUE; + } + + return PatchedOpcode; +} +#endif // DEBUGGING_SUPPORTED + +PEXCEPTION_ROUTINE +RtlVirtualUnwind ( + IN ULONG HandlerType, + IN ULONG64 ImageBase, + IN ULONG64 ControlPc, + IN PT_RUNTIME_FUNCTION FunctionEntry, + IN OUT PCONTEXT ContextRecord, + OUT PVOID *HandlerData, + OUT PULONG64 EstablisherFrame, + IN OUT PKNONVOLATILE_CONTEXT_POINTERS ContextPointers OPTIONAL + ) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + } + CONTRACTL_END; + + // The indirection should be taken care of by the caller + _ASSERTE((FunctionEntry->UnwindData & RUNTIME_FUNCTION_INDIRECT) == 0); + +#ifdef DEBUGGING_SUPPORTED + if (CORDebuggerAttached()) + { + return RtlVirtualUnwind_Worker(HandlerType, ImageBase, ControlPc, FunctionEntry, ContextRecord, HandlerData, EstablisherFrame, ContextPointers); + } + else +#endif // DEBUGGING_SUPPORTED + { + return RtlVirtualUnwind_Unsafe(HandlerType, ImageBase, ControlPc, FunctionEntry, ContextRecord, HandlerData, EstablisherFrame, ContextPointers); + } +} + +#ifdef DEBUGGING_SUPPORTED +PEXCEPTION_ROUTINE +RtlVirtualUnwind_Worker ( + IN ULONG HandlerType, + IN ULONG64 ImageBase, + IN ULONG64 ControlPc, + IN PT_RUNTIME_FUNCTION FunctionEntry, + IN OUT PCONTEXT ContextRecord, + OUT PVOID *HandlerData, + OUT PULONG64 EstablisherFrame, + IN OUT PKNONVOLATILE_CONTEXT_POINTERS ContextPointers OPTIONAL + ) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + } + CONTRACTL_END; + + // b/c we're only called by the safe RtlVirtualUnwind we are guaranteed + // that the debugger is attched when we get here. + _ASSERTE(CORDebuggerAttached()); + + LOG((LF_CORDB, LL_EVERYTHING, "RVU_CBSW: in RtlVitualUnwind_ClrDbgSafeWorker, ControlPc=0x%p\n", ControlPc)); + + BOOL InEpilogue = FALSE; + BOOL HasManagedBreakpoint = FALSE; + BOOL HasUnmanagedBreakpoint = FALSE; + UCHAR TempOpcode = NULL; + PUCHAR NextByte; + ULONG CurrentOffset; + ULONG FrameRegister; + ULONG64 BranchTarget; + PUNWIND_INFO UnwindInfo; + + // 64bit Whidbey does NOT support interop debugging, so if this + // is not managed code, normal unwind + if (!ExecutionManager::IsManagedCode((PCODE) ControlPc)) + { + goto NORMAL_UNWIND; + } + + UnwindInfo = (PUNWIND_INFO)(FunctionEntry->UnwindData + ImageBase); + CurrentOffset = (ULONG)(ControlPc - (FunctionEntry->BeginAddress + ImageBase)); + + // control stopped in prologue, normal unwind + if (CurrentOffset < UnwindInfo->SizeOfProlog) + { + goto NORMAL_UNWIND; + } + + // ASSUMPTION: only the first byte of an opcode will be patched by the CLR debugging code + + // determine if we're in an epilog and if there is at least one managed breakpoint + NextByte = (PUCHAR)ControlPc; + + TempOpcode = GetOpcodeFromManagedBPForAddress((ULONG64)NextByte, &HasManagedBreakpoint, &HasUnmanagedBreakpoint); + + // TempOpcode == NextByte[0] unless NextByte[0] is a breakpoint + _ASSERTE(TempOpcode == NextByte[0] || NextByte[0] == AMD64_INT3); + + // Check for an indication of the start of a epilogue: + // add rsp, imm8 + // add rsp, imm32 + // lea rsp, -disp8[fp] + // lea rsp, -disp32[fp] + if ((TempOpcode == AMD64_SIZE64_PREFIX) + && (NextByte[1] == AMD64_ADD_IMM8_OP) + && (NextByte[2] == 0xc4)) + { + // add rsp, imm8. + NextByte += 4; + } + else if ((TempOpcode == AMD64_SIZE64_PREFIX) + && (NextByte[1] == AMD64_ADD_IMM32_OP) + && (NextByte[2] == 0xc4)) + { + // add rsp, imm32. + NextByte += 7; + } + else if (((TempOpcode & 0xf8) == AMD64_SIZE64_PREFIX) + && (NextByte[1] == AMD64_LEA_OP)) + { + FrameRegister = ((TempOpcode & 0x7) << 3) | (NextByte[2] & 0x7); + + if ((FrameRegister != 0) + && (FrameRegister == UnwindInfo->FrameRegister)) + { + if ((NextByte[2] & 0xf8) == 0x60) + { + // lea rsp, disp8[fp]. + NextByte += 4; + } + else if ((NextByte[2] &0xf8) == 0xa0) + { + // lea rsp, disp32[fp]. + NextByte += 7; + } + } + } + + // if we haven't eaten any of the code stream detecting a stack adjustment + // then TempOpcode is still valid + if (((ULONG64)NextByte) != ControlPc) + { + TempOpcode = GetOpcodeFromManagedBPForAddress((ULONG64)NextByte, &HasManagedBreakpoint, &HasUnmanagedBreakpoint); + } + + // TempOpcode == NextByte[0] unless NextByte[0] is a breakpoint + _ASSERTE(TempOpcode == NextByte[0] || NextByte[0] == AMD64_INT3); + + // Check for any number of: + // pop nonvolatile-integer-register[0..15]. + while (TRUE) + { + if ((TempOpcode & 0xf8) == AMD64_POP_OP) + { + NextByte += 1; + } + else if (AMD64_IS_REX_PREFIX(TempOpcode) + && ((NextByte[1] & 0xf8) == AMD64_POP_OP)) + { + NextByte += 2; + } + else + { + // when we break out here TempOpcode will hold the next Opcode so there + // is no need to call GetOpcodeFromManagedBPForAddress again + break; + } + TempOpcode = GetOpcodeFromManagedBPForAddress((ULONG64)NextByte, &HasManagedBreakpoint, &HasUnmanagedBreakpoint); + + // TempOpcode == NextByte[0] unless NextByte[0] is a breakpoint + _ASSERTE(TempOpcode == NextByte[0] || NextByte[0] == AMD64_INT3); + } + + // TempOpcode == NextByte[0] unless NextByte[0] is a breakpoint + _ASSERTE(TempOpcode == NextByte[0] || NextByte[0] == AMD64_INT3); + + // If the next instruction is a return, then control is currently in + // an epilogue and execution of the epilogue should be emulated. + // Otherwise, execution is not in an epilogue and the prologue should + // be unwound. + if (TempOpcode == AMD64_RET_OP || TempOpcode == AMD64_RET_OP_2) + { + // A return is an unambiguous indication of an epilogue + InEpilogue = TRUE; + NextByte += 1; + } + else if (TempOpcode == AMD64_REP_PREFIX && NextByte[1] == AMD64_RET_OP) + { + // A return is an unambiguous indication of an epilogue + InEpilogue = TRUE; + NextByte += 2; + } + else if (TempOpcode == AMD64_JMP_IMM8_OP || TempOpcode == AMD64_JMP_IMM32_OP) + { + // An unconditional branch to a target that is equal to the start of + // or outside of this routine is logically a call to another function. + BranchTarget = (ULONG64)NextByte - ImageBase; + + if (TempOpcode == AMD64_JMP_IMM8_OP) + { + BranchTarget += 2 + (CHAR)NextByte[1]; + NextByte += 2; + } + else + { + BranchTarget += 5 + *((LONG UNALIGNED *)&NextByte[1]); + NextByte += 5; + } + + // Now determine whether the branch target refers to code within this + // function. If not, then it is an epilogue indicator. + // + // A branch to the start of self implies a recursive call, so + // is treated as an epilogue. + if (BranchTarget <= FunctionEntry->BeginAddress || + BranchTarget >= FunctionEntry->EndAddress) + { + _ASSERTE((UnwindInfo->Flags & UNW_FLAG_CHAININFO) == 0); + InEpilogue = TRUE; + } + } + else if ((TempOpcode == AMD64_JMP_IND_OP) && (NextByte[1] == 0x25)) + { + // An unconditional jump indirect. + + // This is a jmp outside of the function, probably a tail call + // to an import function. + InEpilogue = TRUE; + NextByte += 2; + } + else if (((TempOpcode & 0xf8) == AMD64_SIZE64_PREFIX) + && (NextByte[1] == AMD64_JMP_IND_OP) + && (NextByte[2] & 0x38) == AMD64_JMP_IND_RAX) + { + // + // This is an indirect jump opcode: 0x48 0xff /4. The 64-bit + // flag (REX.W) is always redundant here, so its presence is + // overloaded to indicate a branch out of the function - a tail + // call. + // + // Such an opcode is an unambiguous epilogue indication. + // + InEpilogue = TRUE; + NextByte += 3; + } + + if (InEpilogue && HasUnmanagedBreakpoint) + { + STRESS_LOG1(LF_CORDB, LL_ERROR, "RtlVirtualUnwind is about to fail b/c the ControlPc (0x%p) is in the epilog of a function which has a 0xCC in its epilog.", ControlPc); + _ASSERTE(!"RtlVirtualUnwind is about to fail b/c you are unwinding through\n" + "the epilogue of a function and have a 0xCC in the codestream. This is\n" + "probably caused by having set that breakpoint yourself in the debugger,\n" + "you might try to remove the bp and ignore this assert."); + } + + if (!(InEpilogue && HasManagedBreakpoint)) + { + goto NORMAL_UNWIND; + } + else + { + // InEpilogue && HasManagedBreakpoint, this means we have to make the fake code buffer + + // We explicitly handle the case where the new below can't allocate, but we're still + // getting an assert from inside new b/c we can be called within a FAULT_FORBID scope. + // + // If new does fail we will still end up crashing, but the debugger doesn't have to + // be OOM hardened in Whidbey and this is a debugger only code path so we're ok in + // that department. + FAULT_NOT_FATAL(); + + LOG((LF_CORDB, LL_EVERYTHING, "RVU_CBSW: Function has >1 managed bp in the epilogue, and we are in the epilogue, need a code buffer for RtlVirtualUnwind\n")); + + // IMPLEMENTATION NOTE: + // It is of note that we are significantly pruning the funtion here in making the fake + // code buffer, all that we are making room for is 1 byte for the prologue, 1 byte for + // function code and what is left of the epilogue to be executed. This is _very_ closely + // tied to the implmentation of RtlVirtualUnwind and the knowledge that by passing the + // the test above and having InEpilogue==TRUE then the code path which will be followed + // through RtlVirtualUnwind is known. + // + // In making this fake code buffer we need to ensure that we don't mess with the outcome + // of the test in RtlVirtualUnwind to determine that control stopped within a function + // epilogue, or the unwinding that will happen when that test comes out TRUE. To that end + // we have preserved a single byte representing the Prologue as a section of the buffer + // as well as a single byte representation of the Function code so that tests to make sure + // that we're out of the prologue will not fail. + + T_RUNTIME_FUNCTION FakeFunctionEntry; + + // + // The buffer contains 4 sections + // + // UNWIND_INFO: The fake UNWIND_INFO will be first, we are making a copy within the + // buffer because it needs to be addressable through a 32bit offset + // of NewImageBase like the fake code buffer + // + // Prologue: A single byte representing the function Prologue + // + // Function Code: A single byte representing the Function's code + // + // Epilogue: This contains what is left to be executed of the Epilogue which control + // stopped in, it can be as little as a "return" type statement or as much + // as the whole Epilogue containing a stack adjustment, pops and "return" + // type statement. + // + // + // Here is the layout of the buffer: + // + // UNWIND_INFO copy: + // pBuffer[0] + // ... + // pBuffer[sizeof(UNWIND_INFO) - 1] + // PROLOGUE: + // pBuffer[sizeof(UNWIND_INFO) + 0] <----------------- THIS IS THE START OF pCodeBuffer + // FUNCTION CODE: + // pBuffer[sizeof(UNWIND_INFO) + FAKE_PROLOG_SIZE] + // EPILOGUE + // pBuffer[sizeof(UNWIND_INFO) + FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE] + // ... + // pBuffer[sizeof(UNWIND_INFO) + FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE + SizeOfEpilogue] + // + ULONG SizeOfEpilogue = (ULONG)((ULONG64)NextByte - ControlPc); + ULONG SizeOfBuffer = (ULONG)(sizeof(UNWIND_INFO) + FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE + SizeOfEpilogue); + BYTE *pBuffer = (BYTE*) new (nothrow) BYTE[SizeOfBuffer]; + BYTE *pCodeBuffer; + ULONG64 NewImageBase; + ULONG64 NewControlPc; + + // This WILL fail during unwind because we KNOW there is a managed breakpoint + // in the epilog and we're in the epilog, but we could not allocate a buffer to + // put our cleaned up code into, what to do? + if (pBuffer == NULL) + { + // TODO: can we throw OOM here? or will we just go recursive b/c that will eventually get to the same place? + _ASSERTE(!"OOM when trying to allocate buffer for virtual unwind cleaned code, BIG PROBLEM!!"); + goto NORMAL_UNWIND; + } + + NewImageBase = ((((ULONG64)pBuffer) >> 32) << 32); + pCodeBuffer = pBuffer + sizeof(UNWIND_INFO); + +#if defined(_DEBUG) + // Fill the buffer up to the rest of the epilogue to be executed with Int3 + for (int i=0; i<(FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE); i++) + { + pCodeBuffer[i] = AMD64_INT3; + } +#endif + + // Copy the UNWIND_INFO and the Epilogue into the buffer + memcpy(pBuffer, (const void*)UnwindInfo, sizeof(UNWIND_INFO)); + memcpy(&(pCodeBuffer[FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE]), (const void*)(BYTE*)ControlPc, SizeOfEpilogue); + + _ASSERTE((UCHAR)*(BYTE*)ControlPc == (UCHAR)pCodeBuffer[FAKE_PROLOG_SIZE+FAKE_FUNCTION_CODE_SIZE]); + + HasManagedBreakpoint = FALSE; + HasUnmanagedBreakpoint = FALSE; + + // The buffer cleaning implementation here just runs through the buffer byte by byte trying + // to get a real opcode from the patch table for any 0xCC that it finds. There is the + // possiblity that the epilogue will contain a 0xCC in an immediate value for which a + // patch won't be found and this will report a false positive for HasUnmanagedBreakpoint. + BYTE* pCleanCodePc = pCodeBuffer + FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE; + BYTE* pRealCodePc = (BYTE*)ControlPc; + while (pCleanCodePc < (pCodeBuffer + FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE + SizeOfEpilogue)) + { + // If we have a breakpoint at the address then try to get the correct opcode from + // the managed patch using GetOpcodeFromManagedBPForAddress. + if (AMD64_INT3 == ((UCHAR)*pCleanCodePc)) + { + (*pCleanCodePc) = GetOpcodeFromManagedBPForAddress((ULONG64)pRealCodePc, &HasManagedBreakpoint, &HasUnmanagedBreakpoint); + } + + pCleanCodePc++; + pRealCodePc++; + } + + // On the second pass through the epilogue assuming things are working as + // they should we should once again have at least one managed breakpoint... + // otherwise why are we here? + _ASSERTE(HasManagedBreakpoint == TRUE); + + // This would be nice to assert, but we can't w/ current buffer cleaning implementation, see note above. + // _ASSERTE(HasUnmanagedBreakpoint == FALSE); + + ((PUNWIND_INFO)pBuffer)->SizeOfProlog = FAKE_PROLOG_SIZE; + + FakeFunctionEntry.BeginAddress = (ULONG)((ULONG64)pCodeBuffer - NewImageBase); + FakeFunctionEntry.EndAddress = (ULONG)((ULONG64)(pCodeBuffer + (FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE + SizeOfEpilogue)) - NewImageBase); + FakeFunctionEntry.UnwindData = (ULONG)((ULONG64)pBuffer - NewImageBase); + + NewControlPc = (ULONG64)(pCodeBuffer + FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE); + + RtlVirtualUnwind_Unsafe((ULONG)HandlerType, (ULONG64)NewImageBase, (ULONG64)NewControlPc, &FakeFunctionEntry, ContextRecord, HandlerData, EstablisherFrame, ContextPointers); + + // Make sure to delete the whole buffer and not just the code buffer + delete[] pBuffer; + + return NULL; // if control left in the epilog then RtlVirtualUnwind will not return an exception handler + } + +NORMAL_UNWIND: + return RtlVirtualUnwind_Unsafe(HandlerType, ImageBase, ControlPc, FunctionEntry, ContextRecord, HandlerData, EstablisherFrame, ContextPointers); +} +#endif // DEBUGGING_SUPPORTED + +#undef FAKE_PROLOG_SIZE +#undef FAKE_FUNCTION_CODE_SIZE + +#undef AMD64_SIZE64_PREFIX +#undef AMD64_ADD_IMM8_OP +#undef AMD64_ADD_IMM32_OP +#undef AMD64_JMP_IMM8_OP +#undef AMD64_JMP_IMM32_OP +#undef AMD64_JMP_IND_OP +#undef AMD64_JMP_IND_RAX +#undef AMD64_POP_OP +#undef AMD64_RET_OP +#undef AMD64_RET_OP_2 +#undef AMD64_NOP +#undef AMD64_INT3 + +#endif // !DACCESS_COMPILE + +#ifndef DACCESS_COMPILE +// Returns TRUE if caller should resume execution. +BOOL +AdjustContextForVirtualStub( + EXCEPTION_RECORD *pExceptionRecord, + CONTEXT *pContext) +{ + LIMITED_METHOD_CONTRACT; + + // Nothing to adjust + + return FALSE; +} + +#endif + diff --git a/src/vm/amd64/excepcpu.h b/src/vm/amd64/excepcpu.h new file mode 100644 index 0000000000..150416608b --- /dev/null +++ b/src/vm/amd64/excepcpu.h @@ -0,0 +1,91 @@ +// 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. +// + +// +// EXCEPCPU.H - +// +// This header file is included from Excep.h if the target platform is AMD64 +// + + +#ifndef __excepamd64_h__ +#define __excepamd64_h__ + +#include "corerror.h" // HResults for the COM+ Runtime + +#include "../dlls/mscorrc/resource.h" + +class FaultingExceptionFrame; + + +#define THROW_CONTROL_FOR_THREAD_FUNCTION RedirectForThrowControl + +EXTERN_C void RedirectForThrowControl(); + +#define STATUS_CLR_GCCOVER_CODE STATUS_PRIVILEGED_INSTRUCTION + +// +// No FS:0, nothing to do. +// +#define INSTALL_EXCEPTION_HANDLING_RECORD(record) +#define UNINSTALL_EXCEPTION_HANDLING_RECORD(record) + +// +// On Win64, the COMPlusFrameHandler's work is done by our personality routine. +// +#define DECLARE_CPFH_EH_RECORD(pCurThread) + +// +// Retrieves the redirected CONTEXT* from the stack frame of one of the +// RedirectedHandledJITCaseForXXX_Stub's. +// +PTR_CONTEXT GetCONTEXTFromRedirectedStubStackFrame(DISPATCHER_CONTEXT * pDispatcherContext); +PTR_CONTEXT GetCONTEXTFromRedirectedStubStackFrame(CONTEXT * pContext); + +// +// Retrieves the FaultingExceptionFrame* from the stack frame of +// RedirectForThrowControl or NakedThrowHelper. +// +FaultingExceptionFrame *GetFrameFromRedirectedStubStackFrame (DISPATCHER_CONTEXT *pDispatcherContext); + +// +// Functions that wrap RtlVirtualUnwind to make sure that in the AMD64 case all the +// breakpoints have been removed from the Epilogue if RtlVirtualUnwind is going to +// try and disassemble it. +// +#if !defined(DACCESS_COMPILE) +UCHAR GetOpcodeFromManagedBPForAddress(ULONG64 Address, BOOL* HasManagedBreakpoint, BOOL* HasUnmanagedBreakpoint); + +#define RtlVirtualUnwind RtlVirtualUnwind_Wrapper + +PEXCEPTION_ROUTINE +RtlVirtualUnwind ( + IN ULONG HandlerType, + IN ULONG64 ImageBase, + IN ULONG64 ControlPc, + IN PT_RUNTIME_FUNCTION FunctionEntry, + IN OUT PCONTEXT ContextRecord, + OUT PVOID *HandlerData, + OUT PULONG64 EstablisherFrame, + IN OUT PKNONVOLATILE_CONTEXT_POINTERS ContextPointers OPTIONAL + ); + +PEXCEPTION_ROUTINE +RtlVirtualUnwind_Worker ( + IN ULONG HandlerType, + IN ULONG64 ImageBase, + IN ULONG64 ControlPc, + IN PT_RUNTIME_FUNCTION FunctionEntry, + IN OUT PCONTEXT ContextRecord, + OUT PVOID *HandlerData, + OUT PULONG64 EstablisherFrame, + IN OUT PKNONVOLATILE_CONTEXT_POINTERS ContextPointers OPTIONAL + ); +#endif // !DACCESS_COMPILE + +BOOL AdjustContextForVirtualStub(EXCEPTION_RECORD *pExceptionRecord, CONTEXT *pContext); + +#endif // __excepamd64_h__ + diff --git a/src/vm/amd64/externalmethodfixupthunk.S b/src/vm/amd64/externalmethodfixupthunk.S new file mode 100644 index 0000000000..b5c3780671 --- /dev/null +++ b/src/vm/amd64/externalmethodfixupthunk.S @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +; +.intel_syntax noprefix +#include "unixasmmacros.inc" +#include "asmconstants.h" + +//============================================================================================ +// EXTERN_C VOID __stdcall ExternalMethodFixupStub() + +NESTED_ENTRY ExternalMethodFixupStub, _TEXT, NoHandler + + PROLOG_WITH_TRANSITION_BLOCK 0, 8, rsi, 0, 0 + + lea rdi, [rsp + __PWTB_TransitionBlock] // pTransitionBlock + sub rsi, 5 // pThunk + mov rdx, 0 // sectionIndex + mov rcx, 0 // pModule + + call C_FUNC(ExternalMethodFixupWorker) + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL +PATCH_LABEL ExternalMethodFixupPatchLabel + TAILJMP_RAX + +NESTED_END ExternalMethodFixupStub, _TEXT + +#ifdef FEATURE_READYTORUN + +NESTED_ENTRY DelayLoad_MethodCall, _TEXT, NoHandler + + PROLOG_WITH_TRANSITION_BLOCK 0, 0x10, rdx, rcx, 0 + + lea rdi, [rsp + __PWTB_TransitionBlock] // pTransitionBlock + mov rsi, rax // pIndirection + + call C_FUNC(ExternalMethodFixupWorker) + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + + // Share the patch label + jmp C_FUNC(ExternalMethodFixupPatchLabel) + +NESTED_END DelayLoad_MethodCall, _TEXT + +//============================================================================================ + +.macro DYNAMICHELPER frameFlags, suffix + +NESTED_ENTRY DelayLoad_Helper\suffix, _TEXT, NoHandler + + PROLOG_WITH_TRANSITION_BLOCK 0, 0x10, rdx, rcx, 0 + + mov r8, \frameFlags + lea rdi, [rsp + __PWTB_TransitionBlock] // pTransitionBlock + mov rsi, rax // pIndirection + + call C_FUNC(DynamicHelperWorker) + + test rax,rax + jnz LOCAL_LABEL(TailCallDelayLoad_Helper\suffix) + + // The result is stored in the argument area of the transition block + mov rax, [rsp + __PWTB_TransitionBlock + OFFSETOF__TransitionBlock__m_argumentRegisters] + + EPILOG_WITH_TRANSITION_BLOCK_RETURN + +LOCAL_LABEL(TailCallDelayLoad_Helper\suffix): + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + TAILJMP_RAX + +NESTED_END DelayLoad_Helper\suffix, _TEXT + + .endm + +DYNAMICHELPER DynamicHelperFrameFlags_Default +DYNAMICHELPER DynamicHelperFrameFlags_ObjectArg, _Obj +DYNAMICHELPER (DynamicHelperFrameFlags_ObjectArg | DynamicHelperFrameFlags_ObjectArg2), _ObjObj + +#endif // FEATURE_READYTORUN + +//============================================================================================ +// EXTERN_C VOID __stdcall VirtualMethodFixupStub() + +NESTED_ENTRY VirtualMethodFixupStub, _TEXT, NoHandler + + PROLOG_WITH_TRANSITION_BLOCK 0, 8, rsi, 0, 0 + + lea rdi, [rsp + __PWTB_TransitionBlock] // pTransitionBlock + sub rsi, 5 // pThunk + call C_FUNC(VirtualMethodFixupWorker) + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL +PATCH_LABEL VirtualMethodFixupPatchLabel + TAILJMP_RAX + +NESTED_END VirtualMethodFixupStub, _TEXT diff --git a/src/vm/amd64/getstate.S b/src/vm/amd64/getstate.S new file mode 100644 index 0000000000..5c3e85cb41 --- /dev/null +++ b/src/vm/amd64/getstate.S @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +.intel_syntax noprefix +#include "unixasmmacros.inc" +#include "asmconstants.h" + +LEAF_ENTRY GetCurrentSP, _TEXT + + mov rax, rsp + add rax, 8 + ret + +LEAF_END GetCurrentSP, _TEXT + + +LEAF_ENTRY GetCurrentIP, _TEXT + + mov rax, [rsp] + ret + +LEAF_END GetCurrentIP, _TEXT + + +// EXTERN_C void LazyMachStateCaptureState(struct LazyMachState *pState) +LEAF_ENTRY LazyMachStateCaptureState, _TEXT + + mov rdx, [rsp] // get the return address + + mov [rdi + OFFSETOF__MachState__m_Capture + 0*8], r12 + mov [rdi + OFFSETOF__MachState__m_Capture + 1*8], r13 + mov [rdi + OFFSETOF__MachState__m_Capture + 2*8], r14 + mov [rdi + OFFSETOF__MachState__m_Capture + 3*8], r15 + mov [rdi + OFFSETOF__MachState__m_Capture + 4*8], rbx + mov [rdi + OFFSETOF__MachState__m_Capture + 5*8], rbp + + mov qword ptr [rdi + OFFSETOF__MachState___pRetAddr], 0 + + mov [rdi + OFFSETOF__LazyMachState__m_CaptureRip], rdx + mov [rdi + OFFSETOF__LazyMachState__m_CaptureRsp], rsp + + ret + +LEAF_END LazyMachStateCaptureState, _TEXT diff --git a/src/vm/amd64/getstate.asm b/src/vm/amd64/getstate.asm new file mode 100644 index 0000000000..40d537fd07 --- /dev/null +++ b/src/vm/amd64/getstate.asm @@ -0,0 +1,85 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; ==++== +; + +; +; ==--== + +include AsmMacros.inc +include AsmConstants.inc + + +LEAF_ENTRY GetCurrentSP, _TEXT + + mov rax, rsp + add rax, 8 + ret + +LEAF_END GetCurrentSP, _TEXT + + +LEAF_ENTRY GetCurrentIP, _TEXT + + mov rax, [rsp] + ret + +LEAF_END GetCurrentIP, _TEXT + + +LEAF_ENTRY GetRBP, _TEXT + + mov rax, rbp + ret + +LEAF_END GetRBP, _TEXT + +;// this is the same implementation as the function of the same name in di\amd64\floatconversion.asm and they must +;// remain in sync. + +;// @dbgtodo inspection: remove this function when we remove the ipc event to load the float state + +; extern "C" double FPFillR8(void* fpContextSlot); +LEAF_ENTRY FPFillR8, _TEXT + movdqa xmm0, [rcx] + ret +LEAF_END FPFillR8, _TEXT + + +LEAF_ENTRY get_cycle_count, _TEXT + + rdtsc ; time stamp count ret'd in edx:eax + shl rdx, 32 + mov edx, eax + mov rax, rdx ; return tsc in rax + ret +LEAF_END get_cycle_count, _TEXT + + +; EXTERN_C void LazyMachStateCaptureState(struct LazyMachState *pState) +LEAF_ENTRY LazyMachStateCaptureState, _TEXT + + mov rdx, [rsp] ; get the return address + + mov [rcx + OFFSETOF__MachState__m_Capture + 0*8], rdi + mov [rcx + OFFSETOF__MachState__m_Capture + 1*8], rsi + mov [rcx + OFFSETOF__MachState__m_Capture + 2*8], rbx + mov [rcx + OFFSETOF__MachState__m_Capture + 3*8], rbp + mov [rcx + OFFSETOF__MachState__m_Capture + 4*8], r12 + mov [rcx + OFFSETOF__MachState__m_Capture + 5*8], r13 + mov [rcx + OFFSETOF__MachState__m_Capture + 6*8], r14 + mov [rcx + OFFSETOF__MachState__m_Capture + 7*8], r15 + + mov qword ptr [rcx + OFFSETOF__MachState___pRetAddr], 0 + + mov [rcx + OFFSETOF__LazyMachState__m_CaptureRip], rdx + mov [rcx + OFFSETOF__LazyMachState__m_CaptureRsp], rsp + + ret + +LEAF_END LazyMachStateCaptureState, _TEXT + + + end diff --git a/src/vm/amd64/gmsamd64.cpp b/src/vm/amd64/gmsamd64.cpp new file mode 100644 index 0000000000..d595db0c91 --- /dev/null +++ b/src/vm/amd64/gmsamd64.cpp @@ -0,0 +1,144 @@ +// 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. + +/**************************************************************/ +/* gmsAMD64.cpp */ +/**************************************************************/ + +#include "common.h" +#include "gmscpu.h" + +void LazyMachState::unwindLazyState(LazyMachState* baseState, + MachState* unwoundState, + DWORD threadId, + int funCallDepth /* = 1 */, + HostCallPreference hostCallPreference /* = (HostCallPreference)(-1) */) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + SUPPORTS_DAC; + } + CONTRACTL_END; + + CONTEXT ctx; + KNONVOLATILE_CONTEXT_POINTERS nonVolRegPtrs; + + ctx.Rip = baseState->m_CaptureRip; + ctx.Rsp = baseState->m_CaptureRsp + 8; // +8 for return addr pushed before calling LazyMachStateCaptureState + +#define CALLEE_SAVED_REGISTER(regname) ctx.regname = unwoundState->m_Capture.regname = baseState->m_Capture.regname; + ENUM_CALLEE_SAVED_REGISTERS(); +#undef CALLEE_SAVED_REGISTER + +#if !defined(DACCESS_COMPILE) + + // For DAC, if we get here, it means that the LazyMachState is uninitialized and we have to unwind it. + // The API we use to unwind in DAC is StackWalk64(), which does not support the context pointers. +#define CALLEE_SAVED_REGISTER(regname) nonVolRegPtrs.regname = (PDWORD64)&unwoundState->m_Capture.regname; + ENUM_CALLEE_SAVED_REGISTERS(); +#undef CALLEE_SAVED_REGISTER + +#endif // !DACCESS_COMPILE + + LOG((LF_GCROOTS, LL_INFO100000, "STACKWALK LazyMachState::unwindLazyState(ip:%p,sp:%p)\n", baseState->m_CaptureRip, baseState->m_CaptureRsp)); + + PCODE pvControlPc; + + do + { + +#ifndef FEATURE_PAL + pvControlPc = Thread::VirtualUnwindCallFrame(&ctx, &nonVolRegPtrs); +#else // !FEATURE_PAL + +#if defined(DACCESS_COMPILE) + HRESULT hr = DacVirtualUnwind(threadId, &ctx, &nonVolRegPtrs); + if (FAILED(hr)) + { + DacError(hr); + } +#else + BOOL success = PAL_VirtualUnwind(&ctx, &nonVolRegPtrs); + if (!success) + { + _ASSERTE(!"unwindLazyState: Unwinding failed"); + EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); + } +#endif // DACCESS_COMPILE + + pvControlPc = GetIP(&ctx); +#endif // !FEATURE_PAL + + if (funCallDepth > 0) + { + --funCallDepth; + if (funCallDepth == 0) + break; + } + else + { + // Determine whether given IP resides in JITted code. (It returns nonzero in that case.) + // Use it now to see if we've unwound to managed code yet. + BOOL fFailedReaderLock = FALSE; + BOOL fIsManagedCode = ExecutionManager::IsManagedCode(pvControlPc, hostCallPreference, &fFailedReaderLock); + if (fFailedReaderLock) + { + // We don't know if we would have been able to find a JIT + // manager, because we couldn't enter the reader lock without + // yielding (and our caller doesn't want us to yield). So abort + // now. + + // Invalidate the lazyState we're returning, so the caller knows + // we aborted before we could fully unwind + unwoundState->_pRetAddr = NULL; + return; + } + + if (fIsManagedCode) + break; + } + } + while(TRUE); + + // + // Update unwoundState so that HelperMethodFrameRestoreState knows which + // registers have been potentially modified. + // + + unwoundState->m_Rip = ctx.Rip; + unwoundState->m_Rsp = ctx.Rsp; + + // For DAC, the return value of this function may be used after unwoundState goes out of scope. so we cannot do + // "unwoundState->_pRetAddr = PTR_TADDR(&unwoundState->m_Rip)". + unwoundState->_pRetAddr = PTR_TADDR(unwoundState->m_Rsp - 8); + +#ifdef FEATURE_PAL +#define CALLEE_SAVED_REGISTER(regname) unwoundState->m_Unwound.regname = ctx.regname; + ENUM_CALLEE_SAVED_REGISTERS(); +#undef CALLEE_SAVED_REGISTER +#endif + +#if defined(DACCESS_COMPILE) + + // For DAC, we have to update the registers directly, since we don't have context pointers. +#define CALLEE_SAVED_REGISTER(regname) unwoundState->m_Capture.regname = ctx.regname; + ENUM_CALLEE_SAVED_REGISTERS(); +#undef CALLEE_SAVED_REGISTER + + // Since we don't have context pointers in this case, just assing them to NULL. +#define CALLEE_SAVED_REGISTER(regname) unwoundState->m_Ptrs.p##regname = NULL; + ENUM_CALLEE_SAVED_REGISTERS(); +#undef CALLEE_SAVED_REGISTER + +#else // !DACCESS_COMPILE + +#define CALLEE_SAVED_REGISTER(regname) unwoundState->m_Ptrs.p##regname = PTR_ULONG64(nonVolRegPtrs.regname); + ENUM_CALLEE_SAVED_REGISTERS(); +#undef CALLEE_SAVED_REGISTER + +#endif // DACCESS_COMPILE +} diff --git a/src/vm/amd64/gmscpu.h b/src/vm/amd64/gmscpu.h new file mode 100644 index 0000000000..ff84d18daf --- /dev/null +++ b/src/vm/amd64/gmscpu.h @@ -0,0 +1,186 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/**************************************************************/ +/* gmscpu.h */ +/**************************************************************/ +/* HelperFrame is defines 'GET_STATE(machState)' macro, which + figures out what the state of the machine will be when the + current method returns. It then stores the state in the + JIT_machState structure. */ + +/**************************************************************/ + +#ifndef __gmsAMD64_h__ +#define __gmsAMD64_h__ + +#ifdef _DEBUG +class HelperMethodFrame; +struct MachState; +EXTERN_C MachState* __stdcall HelperMethodFrameConfirmState(HelperMethodFrame* frame, void* esiVal, void* ediVal, void* ebxVal, void* ebpVal); +#endif // _DEBUG + +// A MachState indicates the register state of the processor at some point in time (usually +// just before or after a call is made). It can be made one of two ways. Either explicitly +// (when you for some reason know the values of all the registers), or implicitly using the +// GET_STATE macros. + +typedef DPTR(struct MachState) PTR_MachState; +struct MachState +{ + MachState() + { + LIMITED_METHOD_DAC_CONTRACT; + INDEBUG(memset(this, 0xCC, sizeof(MachState));) + } + + bool isValid() { LIMITED_METHOD_DAC_CONTRACT; _ASSERTE(dac_cast(_pRetAddr) != INVALID_POINTER_CC); return(_pRetAddr != 0); } + TADDR* pRetAddr() { LIMITED_METHOD_DAC_CONTRACT; _ASSERTE(isValid()); return(_pRetAddr); } + TADDR GetRetAddr() { LIMITED_METHOD_DAC_CONTRACT; _ASSERTE(isValid()); return *_pRetAddr; } +#ifndef DACCESS_COMPILE + void SetRetAddr(TADDR* addr) { _ASSERTE(isValid()); _pRetAddr = addr; } +#endif + + friend class HelperMethodFrame; + friend class CheckAsmOffsets; + friend struct LazyMachState; +#ifdef _DEBUG + friend MachState* __stdcall HelperMethodFrameConfirmState(HelperMethodFrame* frame, void* esiVal, void* ediVal, void* ebxVal, void* ebpVal); +#endif + +protected: + PCODE m_Rip; + TADDR m_Rsp; + + // + // These "capture" fields are READ ONLY once initialized by + // LazyMachStateCaptureState because we are racing to update + // the MachState when we do a stackwalk so, we must not update + // any state used to initialize the unwind from the captured + // state to the managed caller. + // + // Note also, that these fields need to be in the base struct + // because the context pointers below may point up to these + // fields. + // + CalleeSavedRegisters m_Capture; + + // context pointers for preserved registers + CalleeSavedRegistersPointers m_Ptrs; + + PTR_TADDR _pRetAddr; + +#ifdef FEATURE_PAL + // On PAL, we don't always have the context pointers available due to + // a limitation of an unwinding library. In such case, preserve + // the unwound values. + CalleeSavedRegisters m_Unwound; +#endif +}; + +/********************************************************************/ +/* This allows you to defer the computation of the Machine state + until later. Note that we don't reuse slots, because we want + this to be threadsafe without locks */ + +EXTERN_C void LazyMachStateCaptureState(struct LazyMachState *pState); + +typedef DPTR(struct LazyMachState) PTR_LazyMachState; +struct LazyMachState : public MachState +{ + // compute the machine state of the processor as it will exist just + // after the return after at most'funCallDepth' number of functions. + // if 'testFtn' is non-NULL, the return address is tested at each + // return instruction encountered. If this test returns non-NULL, + // then stack walking stops (thus you can walk up to the point that the + // return address matches some criteria + + // Normally this is called with funCallDepth=1 and testFtn = 0 so that + // it returns the state of the processor after the function that called 'captureState()' + void setLazyStateFromUnwind(MachState* copy); + static void unwindLazyState(LazyMachState* baseState, + MachState* lazyState, + DWORD threadId, + int funCallDepth = 1, + HostCallPreference hostCallPreference = AllowHostCalls); + + friend class HelperMethodFrame; + friend class CheckAsmOffsets; + + // + // These "capture" fields are READ ONLY once initialized by + // LazyMachStateCaptureState because we are racing to update + // the MachState when we do a stackwalk so, we must not update + // any state used to initialize the unwind from the captured + // state to the managed caller. + // + ULONG64 m_CaptureRip; + ULONG64 m_CaptureRsp; +}; + +inline void LazyMachState::setLazyStateFromUnwind(MachState* copy) +{ + LIMITED_METHOD_CONTRACT; + +#if defined(DACCESS_COMPILE) + // This function cannot be called in DAC because DAC cannot update target memory. + DacError(E_FAIL); + return; + +#else // !DACCESS_COMPILE + this->m_Rip = copy->m_Rip; + this->m_Rsp = copy->m_Rsp; + +#ifdef FEATURE_PAL + this->m_Unwound = copy->m_Unwound; +#endif + + // Capture* has already been set, so there is no need to touch it + + // loop over the nonvolatile context pointers and make + // sure to properly copy interior pointers into the + // new struct + + PULONG64* pSrc = (PULONG64 *)©->m_Ptrs; + PULONG64* pDst = (PULONG64 *)&this->m_Ptrs; + + const PULONG64 LowerBoundDst = (PULONG64) this; + const PULONG64 LowerBoundSrc = (PULONG64) copy; + + const PULONG64 UpperBoundSrc = (PULONG64) ((BYTE*)LowerBoundSrc + sizeof(*copy)); + + for (int i = 0; i < NUM_CALLEE_SAVED_REGISTERS; i++) + { + PULONG64 valueSrc = *pSrc++; + + if ((LowerBoundSrc <= valueSrc) && (valueSrc < UpperBoundSrc)) + { + // make any pointer interior to 'src' interior to 'dst' + valueSrc = (PULONG64)((BYTE*)valueSrc - (BYTE*)LowerBoundSrc + (BYTE*)LowerBoundDst); + } + + *pDst++ = valueSrc; + } + + // this has to be last because we depend on write ordering to + // synchronize the race implicit in updating this struct + VolatileStore(&_pRetAddr, (PTR_TADDR)(TADDR)&m_Rip); + +#endif // !DACCESS_COMPILE +} + +// Do the initial capture of the machine state. This is meant to be +// as light weight as possible, as we may never need the state that +// we capture. Thus to complete the process you need to call +// 'getMachState()', which finishes the process +EXTERN_C void LazyMachStateCaptureState(struct LazyMachState *pState); + +// CAPTURE_STATE captures just enough register state so that the state of the +// processor can be deterined just after the the routine that has CAPTURE_STATE in +// it returns. + +#define CAPTURE_STATE(machState, ret) \ + LazyMachStateCaptureState(machState) + +#endif // __gmsAMD64_h__ diff --git a/src/vm/amd64/jithelpers_fast.S b/src/vm/amd64/jithelpers_fast.S new file mode 100644 index 0000000000..8076655ad9 --- /dev/null +++ b/src/vm/amd64/jithelpers_fast.S @@ -0,0 +1,473 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +.intel_syntax noprefix +#include "unixasmmacros.inc" +#include "asmconstants.h" + +// Mark start of the code region that we patch at runtime +LEAF_ENTRY JIT_PatchedCodeStart, _TEXT + ret +LEAF_END JIT_PatchedCodeStart, _TEXT + + +// There is an even more optimized version of these helpers possible which takes +// advantage of knowledge of which way the ephemeral heap is growing to only do 1/2 +// that check (this is more significant in the JIT_WriteBarrier case). +// +// Additionally we can look into providing helpers which will take the src/dest from +// specific registers (like x86) which _could_ (??) make for easier register allocation +// for the JIT64, however it might lead to having to have some nasty code that treats +// these guys really special like... :(. +// +// Version that does the move, checks whether or not it's in the GC and whether or not +// it needs to have it's card updated +// +// void JIT_CheckedWriteBarrier(Object** dst, Object* src) +LEAF_ENTRY JIT_CheckedWriteBarrier, _TEXT + + // When WRITE_BARRIER_CHECK is defined _NotInHeap will write the reference + // but if it isn't then it will just return. + // + // See if this is in GCHeap + PREPARE_EXTERNAL_VAR g_lowest_address, rax + cmp rdi, [rax] + // jb NotInHeap + .byte 0x72, 0x0e + PREPARE_EXTERNAL_VAR g_highest_address, rax + cmp rdi, [rax] + // jnb NotInHeap + .byte 0x73, 0x02 + + // call C_FUNC(JIT_WriteBarrier) + .byte 0xeb, 0x05 + + NotInHeap: + // See comment above about possible AV + mov [rdi], rsi + ret +LEAF_END_MARKED JIT_CheckedWriteBarrier, _TEXT + + +// This is used by the mechanism to hold either the JIT_WriteBarrier_PreGrow +// or JIT_WriteBarrier_PostGrow code (depending on the state of the GC). It _WILL_ +// change at runtime as the GC changes. Initially it should simply be a copy of the +// larger of the two functions (JIT_WriteBarrier_PostGrow) to ensure we have created +// enough space to copy that code in. +.balign 16 +LEAF_ENTRY JIT_WriteBarrier, _TEXT +#ifdef _DEBUG + // In debug builds, this just contains jump to the debug version of the write barrier by default + jmp C_FUNC(JIT_WriteBarrier_Debug) +#endif + +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + // JIT_WriteBarrier_WriteWatch_PostGrow64 + + // Regarding patchable constants: + // - 64-bit constants have to be loaded into a register + // - The constants have to be aligned to 8 bytes so that they can be patched easily + // - The constant loads have been located to minimize NOP padding required to align the constants + // - Using different registers for successive constant loads helps pipeline better. Should we decide to use a special + // non-volatile calling convention, this should be changed to use just one register. + + // Do the move into the GC . It is correct to take an AV here, the EH code + // figures out that this came from a WriteBarrier and correctly maps it back + // to the managed method which called the WriteBarrier (see setup in + // InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rdi], rsi + + // Update the write watch table if necessary + mov rax, rdi + movabs r10, 0xF0F0F0F0F0F0F0F0 + shr rax, 0Ch // SoftwareWriteWatch::AddressToTableByteIndexShift + NOP_2_BYTE // padding for alignment of constant + movabs r11, 0xF0F0F0F0F0F0F0F0 + add rax, r10 + cmp byte ptr [rax], 0h + .byte 0x75, 0x06 + // jne CheckCardTable + mov byte ptr [rax], 0FFh + + NOP_3_BYTE // padding for alignment of constant + + // Check the lower and upper ephemeral region bounds + CheckCardTable: + cmp rsi, r11 + .byte 0x72,0x3D + // jb Exit + + NOP_3_BYTE // padding for alignment of constant + + movabs r10, 0xF0F0F0F0F0F0F0F0 + + cmp rsi, r10 + .byte 0x73,0x2B + // jae Exit + + nop // padding for alignment of constant + + movabs rax, 0xF0F0F0F0F0F0F0F0 + + // Touch the card table entry, if not already dirty. + shr rdi, 0x0B + cmp byte ptr [rdi + rax], 0FFh + .byte 0x75, 0x02 + // jne UpdateCardTable + REPRET + + UpdateCardTable: + mov byte ptr [rdi + rax], 0FFh + ret + + .balign 16 + Exit: + REPRET +#else + // JIT_WriteBarrier_PostGrow64 + + // Do the move into the GC . It is correct to take an AV here, the EH code + // figures out that this came from a WriteBarrier and correctly maps it back + // to the managed method which called the WriteBarrier (see setup in + // InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rdi], rsi + + NOP_3_BYTE // padding for alignment of constant + + // Can't compare a 64 bit immediate, so we have to move them into a + // register. Values of these immediates will be patched at runtime. + // By using two registers we can pipeline better. Should we decide to use + // a special non-volatile calling convention, this should be changed to + // just one. + + movabs rax, 0xF0F0F0F0F0F0F0F0 + + // Check the lower and upper ephemeral region bounds + cmp rsi, rax + // jb Exit + .byte 0x72, 0x36 + + nop // padding for alignment of constant + + movabs r8, 0xF0F0F0F0F0F0F0F0 + + cmp rsi, r8 + // jae Exit + .byte 0x73, 0x26 + + nop // padding for alignment of constant + + movabs rax, 0xF0F0F0F0F0F0F0F0 + + // Touch the card table entry, if not already dirty. + shr rdi, 0Bh + cmp byte ptr [rdi + rax], 0FFh + // jne UpdateCardTable + .byte 0x75, 0x02 + REPRET + + UpdateCardTable: + mov byte ptr [rdi + rax], 0FFh + ret + + .balign 16 + Exit: + REPRET +#endif + + // make sure this guy is bigger than any of the other guys + .balign 16 + nop +LEAF_END_MARKED JIT_WriteBarrier, _TEXT + +// Mark start of the code region that we patch at runtime +LEAF_ENTRY JIT_PatchedCodeLast, _TEXT + ret +LEAF_END JIT_PatchedCodeLast, _TEXT + +// JIT_ByRefWriteBarrier has weird symantics, see usage in StubLinkerX86.cpp +// +// Entry: +// RDI - address of ref-field (assigned to) +// RSI - address of the data (source) +// +// Note: RyuJIT assumes that all volatile registers can be trashed by +// the CORINFO_HELP_ASSIGN_BYREF helper (i.e. JIT_ByRefWriteBarrier). +// The precise set is defined by RBM_CALLEE_TRASH. +// +// RCX is trashed +// RAX is trashed +// R10 is trashed +// R11 is trashed on Debug build +// Exit: +// RDI, RSI are incremented by SIZEOF(LPVOID) +LEAF_ENTRY JIT_ByRefWriteBarrier, _TEXT + mov rcx, [rsi] + +// If !WRITE_BARRIER_CHECK do the write first, otherwise we might have to do some ShadowGC stuff +#ifndef WRITE_BARRIER_CHECK + // rcx is [rsi] + mov [rdi], rcx +#endif + + // When WRITE_BARRIER_CHECK is defined _NotInHeap will write the reference + // but if it isn't then it will just return. + // + // See if this is in GCHeap + PREPARE_EXTERNAL_VAR g_lowest_address, rax + cmp rdi, [rax] + jb NotInHeap_ByRefWriteBarrier + PREPARE_EXTERNAL_VAR g_highest_address, rax + cmp rdi, [rax] + jnb NotInHeap_ByRefWriteBarrier + +#ifdef WRITE_BARRIER_CHECK + // **ALSO update the shadow GC heap if that is enabled** + // Do not perform the work if g_GCShadow is 0 + PREPARE_EXTERNAL_VAR g_GCShadow, rax + cmp qword ptr [rax], 0 + je NoShadow_ByRefWriteBarrier + + // If we end up outside of the heap don't corrupt random memory + mov r10, rdi + PREPARE_EXTERNAL_VAR g_lowest_address, rax + sub r10, [rax] + jb NoShadow_ByRefWriteBarrier + + // Check that our adjusted destination is somewhere in the shadow gc + PREPARE_EXTERNAL_VAR g_GCShadow, rax + add r10, [rax] + PREPARE_EXTERNAL_VAR g_GCShadowEnd, rax + cmp r10, [rax] + ja NoShadow_ByRefWriteBarrier + + // Write ref into real GC + mov [rdi], rcx + // Write ref into shadow GC + mov [r10], rcx + + // Ensure that the write to the shadow heap occurs before the read from + // the GC heap so that race conditions are caught by INVALIDGCVALUE + mfence + + // Check that GC/ShadowGC values match + mov r11, [rdi] + mov rax, [r10] + cmp rax, r11 + je DoneShadow_ByRefWriteBarrier + movabs r11, INVALIDGCVALUE + mov [r10], r11 + + jmp DoneShadow_ByRefWriteBarrier + + // If we don't have a shadow GC we won't have done the write yet + NoShadow_ByRefWriteBarrier: + mov [rdi], rcx + + // If we had a shadow GC then we already wrote to the real GC at the same time + // as the shadow GC so we want to jump over the real write immediately above. + // Additionally we know for sure that we are inside the heap and therefore don't + // need to replicate the above checks. + DoneShadow_ByRefWriteBarrier: +#endif + +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + // Update the write watch table if necessary + PREPARE_EXTERNAL_VAR g_sw_ww_enabled_for_gc_heap, rax + cmp byte ptr [rax], 0h + je CheckCardTable_ByRefWriteBarrier + mov rax, rdi + shr rax, 0Ch // SoftwareWriteWatch::AddressToTableByteIndexShift + PREPARE_EXTERNAL_VAR g_sw_ww_table, r10 + add rax, qword ptr [r10] + cmp byte ptr [rax], 0h + jne CheckCardTable_ByRefWriteBarrier + mov byte ptr [rax], 0FFh +#endif + + CheckCardTable_ByRefWriteBarrier: + // See if we can just quick out + PREPARE_EXTERNAL_VAR g_ephemeral_low, rax + cmp rcx, [rax] + jb Exit_ByRefWriteBarrier + PREPARE_EXTERNAL_VAR g_ephemeral_high, rax + cmp rcx, [rax] + jnb Exit_ByRefWriteBarrier + + // move current rdi value into rcx and then increment the pointers + mov rcx, rdi + add rsi, 8h + add rdi, 8h + + // Check if we need to update the card table + // Calc pCardByte + shr rcx, 0x0B + PREPARE_EXTERNAL_VAR g_card_table, rax + add rcx, [rax] + + // Check if this card is dirty + cmp byte ptr [rcx], 0FFh + jne UpdateCardTable_ByRefWriteBarrier + REPRET + + UpdateCardTable_ByRefWriteBarrier: + mov byte ptr [rcx], 0FFh + ret + + .balign 16 + NotInHeap_ByRefWriteBarrier: +// If WRITE_BARRIER_CHECK then we won't have already done the mov and should do it here +// If !WRITE_BARRIER_CHECK we want _NotInHeap and _Leave to be the same and have both +// 16 byte aligned. +#ifdef WRITE_BARRIER_CHECK + // rcx is [rsi] + mov [rdi], rcx +#endif + Exit_ByRefWriteBarrier: + // Increment the pointers before leaving + add rdi, 8h + add rsi, 8h + ret +LEAF_END JIT_ByRefWriteBarrier, _TEXT + +// TODO: put definition for this in asmconstants.h +#define CanCast 1 + +//__declspec(naked) void F_CALL_CONV JIT_Stelem_Ref(PtrArray* array, unsigned idx, Object* val) +.balign 16 +LEAF_ENTRY JIT_Stelem_Ref, _TEXT + // check for null PtrArray* + test rdi, rdi + je LOCAL_LABEL(ThrowNullReferenceException) + + // we only want the lower 32-bits of rsi, it might be dirty + or esi, esi + + // check that index is in bounds + cmp esi, dword ptr [rdi + OFFSETOF__PtrArray__m_NumComponents] // 8h -> array size offset + jae LOCAL_LABEL(ThrowIndexOutOfRangeException) + + // r10 = Array MT + mov r10, [rdi] + + // if we're assigning a null object* then we don't need a write barrier + test rdx, rdx + jz LOCAL_LABEL(AssigningNull) + +#ifdef CHECK_APP_DOMAIN_LEAKS + // get Array TypeHandle + mov rcx, [r10 + OFFSETOF__MethodTable__m_ElementType] // 10h -> typehandle offset, + // check for non-MT + test rcx, 2 + jnz LOCAL_LABEL(NoCheck) + + // Check VMflags of element type + mov rcx, [rcx + OFFSETOF__MethodTable__m_pEEClass] + mov ecx, dword ptr [rcx + OFFSETOF__EEClass__m_wAuxFlags] + test ecx, EEClassFlags + jnz C_FUNC(ArrayStoreCheck_Helper) + + LOCAL_LABEL(NoCheck): +#endif + + mov rcx, [r10 + OFFSETOF__MethodTable__m_ElementType] // 10h -> typehandle offset + + // check for exact match + cmp rcx, [rdx] + jne LOCAL_LABEL(NotExactMatch) + + LOCAL_LABEL(DoWrite): + lea rdi, [rdi + 8*rsi] + add rdi, OFFSETOF__PtrArray__m_Array + mov rsi, rdx + + // JIT_WriteBarrier(Object** dst, Object* src) + jmp C_FUNC(JIT_WriteBarrier) + + LOCAL_LABEL(AssigningNull): + // write barrier is not needed for assignment of NULL references + mov [rdi + 8*rsi + OFFSETOF__PtrArray__m_Array], rdx + ret + + LOCAL_LABEL(NotExactMatch): + PREPARE_EXTERNAL_VAR g_pObjectClass, r11 + cmp rcx, [r11] + je LOCAL_LABEL(DoWrite) + + jmp C_FUNC(JIT_Stelem_Ref__ObjIsInstanceOfNoGC_Helper) + + LOCAL_LABEL(ThrowNullReferenceException): + mov rdi, CORINFO_NullReferenceException_ASM + jmp C_FUNC(JIT_InternalThrow) + + LOCAL_LABEL(ThrowIndexOutOfRangeException): + mov rdi, CORINFO_IndexOutOfRangeException_ASM + jmp C_FUNC(JIT_InternalThrow) +LEAF_END JIT_Stelem_Ref, _TEXT + +LEAF_ENTRY JIT_Stelem_Ref__ObjIsInstanceOfNoGC_Helper, _TEXT + push_nonvol_reg rbp + mov rbp, rsp + set_cfa_register rbp, 16 + + sub rsp, 0x20 + mov [rbp - 0x08], rdi + mov [rbp - 0x10], rsi + mov [rbp - 0x18], rdx + + // need to get TypeHandle before setting rcx to be the Obj* because that trashes the PtrArray* + mov rsi, rcx + mov rdi, rdx + + // TypeHandle::CastResult ObjIsInstanceOfNoGC(Object *pElement, TypeHandle toTypeHnd) + call C_FUNC(ObjIsInstanceOfNoGC) + + mov rdi, [rbp - 0x08] + mov rsi, [rbp - 0x10] + mov rdx, [rbp - 0x18] + + RESET_FRAME_WITH_RBP + + cmp eax, CanCast + jne LOCAL_LABEL(NeedCheck) + + lea rdi, [rdi + 8*rsi] + add rdi, OFFSETOF__PtrArray__m_Array + mov rsi, rdx + + // JIT_WriteBarrier(Object** dst, Object* src) + jmp C_FUNC(JIT_WriteBarrier) + + LOCAL_LABEL(NeedCheck): + jmp C_FUNC(JIT_Stelem_Ref__ArrayStoreCheck_Helper) +LEAF_END JIT_Stelem_Ref__ObjIsInstanceOfNoGC_Helper, _TEXT + +// Need to save reg to provide a stack address for the Object* +LEAF_ENTRY JIT_Stelem_Ref__ArrayStoreCheck_Helper, _TEXT + push_nonvol_reg rbp + mov rbp, rsp + set_cfa_register rbp, 16 + + sub rsp, 0x20 + mov [rbp - 0x10], rdi + mov [rbp - 0x18], rsi + mov [rbp - 0x20], rdx + + mov rdi, rsp + lea rsi, [rbp - 0x10] + // HCIMPL2(FC_INNER_RET, ArrayStoreCheck, Object** pElement, PtrArray** pArray) + call C_FUNC(ArrayStoreCheck) + mov rdi, [rbp - 0x10] + mov rsi, [rbp - 0x18] + mov rdx, [rbp - 0x20] + + lea rdi, [rdi + 8*rsi] + add rdi, OFFSETOF__PtrArray__m_Array + mov rsi, rdx + + RESET_FRAME_WITH_RBP + + // JIT_WriteBarrier(Object** dst, Object* src) + jmp C_FUNC(JIT_WriteBarrier) +LEAF_END JIT_Stelem_Ref__ArrayStoreCheck_Helper, _TEXT diff --git a/src/vm/amd64/jithelpers_fastwritebarriers.S b/src/vm/amd64/jithelpers_fastwritebarriers.S new file mode 100644 index 0000000000..6d61b26c26 --- /dev/null +++ b/src/vm/amd64/jithelpers_fastwritebarriers.S @@ -0,0 +1,319 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +.intel_syntax noprefix +#include "unixasmmacros.inc" + + + .balign 8 +LEAF_ENTRY JIT_WriteBarrier_PreGrow64, _TEXT + // Do the move into the GC . It is correct to take an AV here, the EH code + // figures out that this came from a WriteBarrier and correctly maps it back + // to the managed method which called the WriteBarrier (see setup in + // InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rdi], rsi + + NOP_3_BYTE // padding for alignment of constant + + // Can't compare a 64 bit immediate, so we have to move it into a + // register. Value of this immediate will be patched at runtime. +PATCH_LABEL JIT_WriteBarrier_PreGrow64_Patch_Label_Lower + movabs rax, 0xF0F0F0F0F0F0F0F0 + + // Check the lower ephemeral region bound. + cmp rsi, rax + .byte 0x72, 0x23 + // jb Exit_PreGrow64 + + nop // padding for alignment of constant + +PATCH_LABEL JIT_WriteBarrier_PreGrow64_Patch_Label_CardTable + movabs rax, 0xF0F0F0F0F0F0F0F0 + + // Touch the card table entry, if not already dirty. + shr rdi, 0x0B + cmp byte ptr [rdi + rax], 0FFh + .byte 0x75, 0x02 + // jne UpdateCardTable_PreGrow64 + REPRET + + UpdateCardTable_PreGrow64: + mov byte ptr [rdi + rax], 0FFh + ret + + .balign 16 + Exit_PreGrow64: + REPRET +LEAF_END_MARKED JIT_WriteBarrier_PreGrow64, _TEXT + + + .balign 8 +// See comments for JIT_WriteBarrier_PreGrow (above). +LEAF_ENTRY JIT_WriteBarrier_PostGrow64, _TEXT + // Do the move into the GC . It is correct to take an AV here, the EH code + // figures out that this came from a WriteBarrier and correctly maps it back + // to the managed method which called the WriteBarrier (see setup in + // InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rdi], rsi + + NOP_3_BYTE // padding for alignment of constant + + // Can't compare a 64 bit immediate, so we have to move them into a + // register. Values of these immediates will be patched at runtime. + // By using two registers we can pipeline better. Should we decide to use + // a special non-volatile calling convention, this should be changed to + // just one. +PATCH_LABEL JIT_WriteBarrier_PostGrow64_Patch_Label_Lower + movabs rax, 0xF0F0F0F0F0F0F0F0 + + // Check the lower and upper ephemeral region bounds + cmp rsi, rax + .byte 0x72,0x33 + // jb Exit_PostGrow64 + + nop // padding for alignment of constant + +PATCH_LABEL JIT_WriteBarrier_PostGrow64_Patch_Label_Upper + movabs r8, 0xF0F0F0F0F0F0F0F0 + + cmp rsi, r8 + .byte 0x73,0x23 + // jae Exit_PostGrow64 + + nop // padding for alignment of constant + +PATCH_LABEL JIT_WriteBarrier_PostGrow64_Patch_Label_CardTable + movabs rax, 0xF0F0F0F0F0F0F0F0 + + // Touch the card table entry, if not already dirty. + shr rdi, 0x0B + cmp byte ptr [rdi + rax], 0FFh + .byte 0x75, 0x02 + // jne UpdateCardTable_PostGrow64 + REPRET + + UpdateCardTable_PostGrow64: + mov byte ptr [rdi + rax], 0FFh + ret + + .balign 16 + Exit_PostGrow64: + REPRET +LEAF_END_MARKED JIT_WriteBarrier_PostGrow64, _TEXT + + +#ifdef FEATURE_SVR_GC + + .balign 8 +LEAF_ENTRY JIT_WriteBarrier_SVR64, _TEXT + // + // SVR GC has multiple heaps, so it cannot provide one single + // ephemeral region to bounds check against, so we just skip the + // bounds checking all together and do our card table update + // unconditionally. + // + + // Do the move into the GC . It is correct to take an AV here, the EH code + // figures out that this came from a WriteBarrier and correctly maps it back + // to the managed method which called the WriteBarrier (see setup in + // InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rdi], rsi + + NOP_3_BYTE // padding for alignment of constant + +PATCH_LABEL JIT_WriteBarrier_SVR64_PatchLabel_CardTable + movabs rax, 0xF0F0F0F0F0F0F0F0 + + shr rdi, 0x0B + + cmp byte ptr [rdi + rax], 0FFh + .byte 0x75, 0x02 + // jne UpdateCardTable_SVR64 + REPRET + + UpdateCardTable_SVR64: + mov byte ptr [rdi + rax], 0FFh + ret +LEAF_END_MARKED JIT_WriteBarrier_SVR64, _TEXT + +#endif + + +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + + .balign 8 +LEAF_ENTRY JIT_WriteBarrier_WriteWatch_PreGrow64, _TEXT + // Regarding patchable constants: + // - 64-bit constants have to be loaded into a register + // - The constants have to be aligned to 8 bytes so that they can be patched easily + // - The constant loads have been located to minimize NOP padding required to align the constants + // - Using different registers for successive constant loads helps pipeline better. Should we decide to use a special + // non-volatile calling convention, this should be changed to use just one register. + + // Do the move into the GC . It is correct to take an AV here, the EH code + // figures out that this came from a WriteBarrier and correctly maps it back + // to the managed method which called the WriteBarrier (see setup in + // InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rdi], rsi + + // Update the write watch table if necessary + mov rax, rdi +PATCH_LABEL JIT_WriteBarrier_WriteWatch_PreGrow64_Patch_Label_WriteWatchTable + movabs r10, 0xF0F0F0F0F0F0F0F0 + shr rax, 0Ch // SoftwareWriteWatch::AddressToTableByteIndexShift + NOP_2_BYTE // padding for alignment of constant +PATCH_LABEL JIT_WriteBarrier_WriteWatch_PreGrow64_Patch_Label_Lower + movabs r11, 0xF0F0F0F0F0F0F0F0 + add rax, r10 + cmp byte ptr [rax], 0h + .byte 0x75, 0x03 + // jne CheckCardTable_WriteWatch_PreGrow64 + mov byte ptr [rax], 0FFh + + CheckCardTable_WriteWatch_PreGrow64: + // Check the lower ephemeral region bound. + cmp rsi, r11 + .byte 0x72, 0x20 + // jb Exit_WriteWatch_PreGrow64 + + // Touch the card table entry, if not already dirty. + shr rdi, 0x0B + NOP_2_BYTE // padding for alignment of constant +PATCH_LABEL JIT_WriteBarrier_WriteWatch_PreGrow64_Patch_Label_CardTable + movabs rax, 0xF0F0F0F0F0F0F0F0 + cmp byte ptr [rdi + rax], 0FFh + .byte 0x75, 0x02 + // jne UpdateCardTable_WriteWatch_PreGrow64 + REPRET + + UpdateCardTable_WriteWatch_PreGrow64: + mov byte ptr [rdi + rax], 0FFh + ret + + .balign 16 + Exit_WriteWatch_PreGrow64: + REPRET +LEAF_END_MARKED JIT_WriteBarrier_WriteWatch_PreGrow64, _TEXT + + + .balign 8 +LEAF_ENTRY JIT_WriteBarrier_WriteWatch_PostGrow64, _TEXT + // Regarding patchable constants: + // - 64-bit constants have to be loaded into a register + // - The constants have to be aligned to 8 bytes so that they can be patched easily + // - The constant loads have been located to minimize NOP padding required to align the constants + // - Using different registers for successive constant loads helps pipeline better. Should we decide to use a special + // non-volatile calling convention, this should be changed to use just one register. + + // Do the move into the GC . It is correct to take an AV here, the EH code + // figures out that this came from a WriteBarrier and correctly maps it back + // to the managed method which called the WriteBarrier (see setup in + // InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rdi], rsi + + // Update the write watch table if necessary + mov rax, rdi +PATCH_LABEL JIT_WriteBarrier_WriteWatch_PostGrow64_Patch_Label_WriteWatchTable + movabs r10, 0xF0F0F0F0F0F0F0F0 + shr rax, 0Ch // SoftwareWriteWatch::AddressToTableByteIndexShift + NOP_2_BYTE // padding for alignment of constant +PATCH_LABEL JIT_WriteBarrier_WriteWatch_PostGrow64_Patch_Label_Lower + movabs r11, 0xF0F0F0F0F0F0F0F0 + add rax, r10 + cmp byte ptr [rax], 0h + .byte 0x75, 0x06 + // jne CheckCardTable_WriteWatch_PostGrow64 + mov byte ptr [rax], 0FFh + + NOP_3_BYTE // padding for alignment of constant + + // Check the lower and upper ephemeral region bounds + CheckCardTable_WriteWatch_PostGrow64: + cmp rsi, r11 + .byte 0x72, 0x3d + // jb Exit_WriteWatch_PostGrow64 + + NOP_3_BYTE // padding for alignment of constant + +PATCH_LABEL JIT_WriteBarrier_WriteWatch_PostGrow64_Patch_Label_Upper + movabs r10, 0xF0F0F0F0F0F0F0F0 + + cmp rsi, r10 + .byte 0x73, 0x2b + // jae Exit_WriteWatch_PostGrow64 + + nop // padding for alignment of constant + +PATCH_LABEL JIT_WriteBarrier_WriteWatch_PostGrow64_Patch_Label_CardTable + movabs rax, 0xF0F0F0F0F0F0F0F0 + + // Touch the card table entry, if not already dirty. + shr rdi, 0x0B + cmp byte ptr [rdi + rax], 0FFh + .byte 0x75, 0x02 + // jne UpdateCardTable_WriteWatch_PostGrow64 + REPRET + + UpdateCardTable_WriteWatch_PostGrow64: + mov byte ptr [rdi + rax], 0FFh + ret + + .balign 16 + Exit_WriteWatch_PostGrow64: + REPRET +LEAF_END_MARKED JIT_WriteBarrier_WriteWatch_PostGrow64, _TEXT + + +#ifdef FEATURE_SVR_GC + + .balign 8 +LEAF_ENTRY JIT_WriteBarrier_WriteWatch_SVR64, _TEXT + // Regarding patchable constants: + // - 64-bit constants have to be loaded into a register + // - The constants have to be aligned to 8 bytes so that they can be patched easily + // - The constant loads have been located to minimize NOP padding required to align the constants + // - Using different registers for successive constant loads helps pipeline better. Should we decide to use a special + // non-volatile calling convention, this should be changed to use just one register. + + // + // SVR GC has multiple heaps, so it cannot provide one single + // ephemeral region to bounds check against, so we just skip the + // bounds checking all together and do our card table update + // unconditionally. + // + + // Do the move into the GC . It is correct to take an AV here, the EH code + // figures out that this came from a WriteBarrier and correctly maps it back + // to the managed method which called the WriteBarrier (see setup in + // InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rdi], rsi + + // Update the write watch table if necessary + mov rax, rdi +PATCH_LABEL JIT_WriteBarrier_WriteWatch_SVR64_PatchLabel_WriteWatchTable + movabs r10, 0xF0F0F0F0F0F0F0F0 + shr rax, 0Ch // SoftwareWriteWatch::AddressToTableByteIndexShift + NOP_2_BYTE // padding for alignment of constant +PATCH_LABEL JIT_WriteBarrier_WriteWatch_SVR64_PatchLabel_CardTable + movabs r11, 0xF0F0F0F0F0F0F0F0 + add rax, r10 + cmp byte ptr [rax], 0h + .byte 0x75, 0x03 + // jne CheckCardTable_WriteWatch_SVR64 + mov byte ptr [rax], 0FFh + + CheckCardTable_WriteWatch_SVR64: + shr rdi, 0x0B + cmp byte ptr [rdi + r11], 0FFh + .byte 0x75, 0x02 + // jne UpdateCardTable_WriteWatch_SVR64 + REPRET + + UpdateCardTable_WriteWatch_SVR64: + mov byte ptr [rdi + r11], 0FFh + ret +LEAF_END_MARKED JIT_WriteBarrier_WriteWatch_SVR64, _TEXT + +#endif +#endif diff --git a/src/vm/amd64/jithelpers_slow.S b/src/vm/amd64/jithelpers_slow.S new file mode 100644 index 0000000000..0a5da69393 --- /dev/null +++ b/src/vm/amd64/jithelpers_slow.S @@ -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. + +.intel_syntax noprefix +#include "unixasmmacros.inc" + +#ifdef _DEBUG +// Version for when we're sure to be in the GC, checks whether or not the card +// needs to be updated +// +// void JIT_WriteBarrier_Debug(Object** dst, Object* src) +LEAF_ENTRY JIT_WriteBarrier_Debug, _TEXT + +#ifdef WRITE_BARRIER_CHECK + // **ALSO update the shadow GC heap if that is enabled** + // Do not perform the work if g_GCShadow is 0 + PREPARE_EXTERNAL_VAR g_GCShadow, rax + cmp qword ptr [rax], 0 + je NoShadow + + // If we end up outside of the heap don't corrupt random memory + mov r10, rdi + PREPARE_EXTERNAL_VAR g_lowest_address, r11 + sub r10, [r11] + jb NoShadow + + // Check that our adjusted destination is somewhere in the shadow gc + add r10, [rax] + PREPARE_EXTERNAL_VAR g_GCShadowEnd, r11 + cmp r10, [r11] + ja NoShadow + + // Write ref into real GC// see comment below about possibility of AV + mov [rdi], rsi + // Write ref into shadow GC + mov [r10], rsi + + // Ensure that the write to the shadow heap occurs before the read from + // the GC heap so that race conditions are caught by INVALIDGCVALUE + mfence + + // Check that GC/ShadowGC values match + mov r11, [rdi] + mov rax, [r10] + cmp rax, r11 + je DoneShadow + movabs r11, INVALIDGCVALUE + mov [r10], r11 + + jmp DoneShadow + + // If we don't have a shadow GC we won't have done the write yet + NoShadow: +#endif + + mov rax, rsi + + // Do the move. It is correct to possibly take an AV here, the EH code + // figures out that this came from a WriteBarrier and correctly maps it back + // to the managed method which called the WriteBarrier (see setup in + // InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rdi], rax + +#ifdef WRITE_BARRIER_CHECK + // If we had a shadow GC then we already wrote to the real GC at the same time + // as the shadow GC so we want to jump over the real write immediately above + DoneShadow: +#endif + +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + // Update the write watch table if necessary + PREPARE_EXTERNAL_VAR g_sw_ww_enabled_for_gc_heap, r10 + cmp byte ptr [r10], 0h + je CheckCardTable_Debug + mov r10, rdi + shr r10, 0Ch // SoftwareWriteWatch::AddressToTableByteIndexShift + PREPARE_EXTERNAL_VAR g_sw_ww_table, r11 + add r10, qword ptr [r11] + cmp byte ptr [r10], 0h + jne CheckCardTable_Debug + mov byte ptr [r10], 0FFh +#endif + + CheckCardTable_Debug: + // See if we can just quick out + PREPARE_EXTERNAL_VAR g_ephemeral_low, r10 + cmp rax, [r10] + jb Exit_Debug + PREPARE_EXTERNAL_VAR g_ephemeral_high, r10 + cmp rax, [r10] + jnb Exit_Debug + + // Check if we need to update the card table + // Calc pCardByte + shr rdi, 0x0B + PREPARE_EXTERNAL_VAR g_card_table, r10 + add rdi, [r10] + + // Check if this card is dirty + cmp byte ptr [rdi], 0FFh + jne UpdateCardTable_Debug + REPRET + + UpdateCardTable_Debug: + mov byte ptr [rdi], 0FFh + ret + + .balign 16 + Exit_Debug: + REPRET +LEAF_END_MARKED JIT_WriteBarrier_Debug, _TEXT +#endif diff --git a/src/vm/amd64/jithelpersamd64.cpp b/src/vm/amd64/jithelpersamd64.cpp new file mode 100644 index 0000000000..8f0a889fde --- /dev/null +++ b/src/vm/amd64/jithelpersamd64.cpp @@ -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. +// =========================================================================== +// File: JITHelpers.CPP +// =========================================================================== + +// This contains JITinterface routines that are specific to the +// AMD64 platform. They are modeled after the X86 specific routines +// found in JIThelp.asm + + +#include "common.h" +#include "jitinterface.h" +#include "eeconfig.h" +#include "excep.h" +#include "ecall.h" +#include "asmconstants.h" + +EXTERN_C void JIT_TailCallHelperStub_ReturnAddress(); + +TailCallFrame * TailCallFrame::GetFrameFromContext(CONTEXT * pContext) +{ + _ASSERTE((void*)::GetIP(pContext) == JIT_TailCallHelperStub_ReturnAddress); + return (TailCallFrame*)(pContext->R13 + sizeof(GSCookie)); +} + +// Assuming pContext is a plain generic call-site, adjust it to look like +// it called into TailCallHelperStub, and is at the point of the call. +TailCallFrame * TailCallFrame::AdjustContextForTailCallHelperStub(CONTEXT * pContext, size_t cbNewArgArea, Thread * pThread) +{ + TailCallFrame * pNewFrame = (TailCallFrame *)(GetSP(pContext) - sizeof(TailCallFrame)); + + // R13 is the frame pointer (for popping the stack) + pContext->R13 = (size_t)pNewFrame - sizeof(GSCookie); + // R12 is the previous stack pointer, so we can determine if a return buffer from the + // immediate caller (and thus being discarded via the tail call), or someplace else + pContext->R12 = GetSP(pContext); + // for the args and pushed return address of the 'call' + SetSP(pContext, (size_t)pNewFrame - (cbNewArgArea + sizeof(void*) + sizeof(GSCookie))); + + // For popping the Frame, store the Thread + pContext->R14 = (DWORD_PTR)pThread; + // And the current head/top + pContext->R15 = (DWORD_PTR)pThread->GetFrame(); // m_Next + + return (TailCallFrame *) pNewFrame; +} diff --git a/src/vm/amd64/jitinterfaceamd64.cpp b/src/vm/amd64/jitinterfaceamd64.cpp new file mode 100644 index 0000000000..39c2e05c2f --- /dev/null +++ b/src/vm/amd64/jitinterfaceamd64.cpp @@ -0,0 +1,655 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =========================================================================== +// File: JITinterfaceCpu.CPP +// =========================================================================== + +// This contains JITinterface routines that are specific to the +// AMD64 platform. They are modeled after the X86 specific routines +// found in JITinterfaceX86.cpp or JIThelp.asm + + +#include "common.h" +#include "jitinterface.h" +#include "eeconfig.h" +#include "excep.h" +#include "threadsuspend.h" +#include "../../gc/softwarewritewatch.h" + +extern uint8_t* g_ephemeral_low; +extern uint8_t* g_ephemeral_high; +extern uint32_t* g_card_table; + +// Patch Labels for the various write barriers +EXTERN_C void JIT_WriteBarrier_End(); + +EXTERN_C void JIT_WriteBarrier_PreGrow64(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_PreGrow64_Patch_Label_Lower(); +EXTERN_C void JIT_WriteBarrier_PreGrow64_Patch_Label_CardTable(); +EXTERN_C void JIT_WriteBarrier_PreGrow64_End(); + +EXTERN_C void JIT_WriteBarrier_PostGrow64(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_PostGrow64_Patch_Label_Lower(); +EXTERN_C void JIT_WriteBarrier_PostGrow64_Patch_Label_Upper(); +EXTERN_C void JIT_WriteBarrier_PostGrow64_Patch_Label_CardTable(); +EXTERN_C void JIT_WriteBarrier_PostGrow64_End(); + +#ifdef FEATURE_SVR_GC +EXTERN_C void JIT_WriteBarrier_SVR64(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_SVR64_PatchLabel_CardTable(); +EXTERN_C void JIT_WriteBarrier_SVR64_End(); +#endif // FEATURE_SVR_GC + +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP +EXTERN_C void JIT_WriteBarrier_WriteWatch_PreGrow64(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_WriteWatch_PreGrow64_Patch_Label_WriteWatchTable(); +EXTERN_C void JIT_WriteBarrier_WriteWatch_PreGrow64_Patch_Label_Lower(); +EXTERN_C void JIT_WriteBarrier_WriteWatch_PreGrow64_Patch_Label_CardTable(); +EXTERN_C void JIT_WriteBarrier_WriteWatch_PreGrow64_End(); + +EXTERN_C void JIT_WriteBarrier_WriteWatch_PostGrow64(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_WriteWatch_PostGrow64_Patch_Label_WriteWatchTable(); +EXTERN_C void JIT_WriteBarrier_WriteWatch_PostGrow64_Patch_Label_Lower(); +EXTERN_C void JIT_WriteBarrier_WriteWatch_PostGrow64_Patch_Label_Upper(); +EXTERN_C void JIT_WriteBarrier_WriteWatch_PostGrow64_Patch_Label_CardTable(); +EXTERN_C void JIT_WriteBarrier_WriteWatch_PostGrow64_End(); + +#ifdef FEATURE_SVR_GC +EXTERN_C void JIT_WriteBarrier_WriteWatch_SVR64(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_WriteWatch_SVR64_PatchLabel_WriteWatchTable(); +EXTERN_C void JIT_WriteBarrier_WriteWatch_SVR64_PatchLabel_CardTable(); +EXTERN_C void JIT_WriteBarrier_WriteWatch_SVR64_End(); +#endif // FEATURE_SVR_GC +#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + +WriteBarrierManager g_WriteBarrierManager; + +// Use this somewhat hokey macro to concantonate the function start with the patch +// label, this allows the code below to look relatively nice, but relies on the +// naming convention which we have established for these helpers. +#define CALC_PATCH_LOCATION(func,label,offset) CalculatePatchLocation((PVOID)func, (PVOID)func##_##label, offset) + +WriteBarrierManager::WriteBarrierManager() : + m_currentWriteBarrier(WRITE_BARRIER_UNINITIALIZED) +{ + LIMITED_METHOD_CONTRACT; +} + +#ifndef CODECOVERAGE // Deactivate alignment validation for code coverage builds + // because the instrumentation tool will not preserve alignmant constraits and we will fail. + +void WriteBarrierManager::Validate() +{ + CONTRACTL + { + MODE_ANY; + GC_NOTRIGGER; + NOTHROW; + } + CONTRACTL_END; + + // we have an invariant that the addresses of all the values that we update in our write barrier + // helpers must be naturally aligned, this is so that the update can happen atomically since there + // are places where these values are updated while the EE is running + // NOTE: we can't call this from the ctor since our infrastructure isn't ready for assert dialogs + + PBYTE pLowerBoundImmediate, pUpperBoundImmediate, pCardTableImmediate; + + pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PreGrow64, Patch_Label_Lower, 2); + pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PreGrow64, Patch_Label_CardTable, 2); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", (reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", (reinterpret_cast(pCardTableImmediate) & 0x7) == 0); + + pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PostGrow64, Patch_Label_Lower, 2); + pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PostGrow64, Patch_Label_Upper, 2); + pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PostGrow64, Patch_Label_CardTable, 2); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", (reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", (reinterpret_cast(pUpperBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", (reinterpret_cast(pCardTableImmediate) & 0x7) == 0); + +#ifdef FEATURE_SVR_GC + pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_SVR64, PatchLabel_CardTable, 2); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", (reinterpret_cast(pCardTableImmediate) & 0x7) == 0); +#endif // FEATURE_SVR_GC + +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + PBYTE pWriteWatchTableImmediate; + + pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PreGrow64, Patch_Label_WriteWatchTable, 2); + pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PreGrow64, Patch_Label_Lower, 2); + pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PreGrow64, Patch_Label_CardTable, 2); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", (reinterpret_cast(pWriteWatchTableImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", (reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", (reinterpret_cast(pCardTableImmediate) & 0x7) == 0); + + pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_WriteWatchTable, 2); + pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_Lower, 2); + pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_Upper, 2); + pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_CardTable, 2); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", (reinterpret_cast(pWriteWatchTableImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", (reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", (reinterpret_cast(pUpperBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", (reinterpret_cast(pCardTableImmediate) & 0x7) == 0); + +#ifdef FEATURE_SVR_GC + pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_SVR64, PatchLabel_WriteWatchTable, 2); + pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_SVR64, PatchLabel_CardTable, 2); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", (reinterpret_cast(pWriteWatchTableImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", (reinterpret_cast(pCardTableImmediate) & 0x7) == 0); +#endif // FEATURE_SVR_GC +#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP +} + +#endif // CODECOVERAGE + + +PCODE WriteBarrierManager::GetCurrentWriteBarrierCode() +{ + LIMITED_METHOD_CONTRACT; + + switch (m_currentWriteBarrier) + { + case WRITE_BARRIER_PREGROW64: + return GetEEFuncEntryPoint(JIT_WriteBarrier_PreGrow64); + case WRITE_BARRIER_POSTGROW64: + return GetEEFuncEntryPoint(JIT_WriteBarrier_PostGrow64); +#ifdef FEATURE_SVR_GC + case WRITE_BARRIER_SVR64: + return GetEEFuncEntryPoint(JIT_WriteBarrier_SVR64); +#endif // FEATURE_SVR_GC +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + case WRITE_BARRIER_WRITE_WATCH_PREGROW64: + return GetEEFuncEntryPoint(JIT_WriteBarrier_WriteWatch_PreGrow64); + case WRITE_BARRIER_WRITE_WATCH_POSTGROW64: + return GetEEFuncEntryPoint(JIT_WriteBarrier_WriteWatch_PostGrow64); +#ifdef FEATURE_SVR_GC + case WRITE_BARRIER_WRITE_WATCH_SVR64: + return GetEEFuncEntryPoint(JIT_WriteBarrier_WriteWatch_SVR64); +#endif // FEATURE_SVR_GC +#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + default: + UNREACHABLE_MSG("unexpected m_currentWriteBarrier!"); + }; +} + +size_t WriteBarrierManager::GetSpecificWriteBarrierSize(WriteBarrierType writeBarrier) +{ +// marked asm functions are those which use the LEAF_END_MARKED macro to end them which +// creates a public Name_End label which can be used to figure out their size without +// having to create unwind info. +#define MARKED_FUNCTION_SIZE(pfn) (size_t)((LPBYTE)GetEEFuncEntryPoint(pfn##_End) - (LPBYTE)GetEEFuncEntryPoint(pfn)) + + switch (writeBarrier) + { + case WRITE_BARRIER_PREGROW64: + return MARKED_FUNCTION_SIZE(JIT_WriteBarrier_PreGrow64); + case WRITE_BARRIER_POSTGROW64: + return MARKED_FUNCTION_SIZE(JIT_WriteBarrier_PostGrow64); +#ifdef FEATURE_SVR_GC + case WRITE_BARRIER_SVR64: + return MARKED_FUNCTION_SIZE(JIT_WriteBarrier_SVR64); +#endif // FEATURE_SVR_GC +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + case WRITE_BARRIER_WRITE_WATCH_PREGROW64: + return MARKED_FUNCTION_SIZE(JIT_WriteBarrier_WriteWatch_PreGrow64); + case WRITE_BARRIER_WRITE_WATCH_POSTGROW64: + return MARKED_FUNCTION_SIZE(JIT_WriteBarrier_WriteWatch_PostGrow64); +#ifdef FEATURE_SVR_GC + case WRITE_BARRIER_WRITE_WATCH_SVR64: + return MARKED_FUNCTION_SIZE(JIT_WriteBarrier_WriteWatch_SVR64); +#endif // FEATURE_SVR_GC +#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + case WRITE_BARRIER_BUFFER: + return MARKED_FUNCTION_SIZE(JIT_WriteBarrier); + default: + UNREACHABLE_MSG("unexpected m_currentWriteBarrier!"); + }; +#undef MARKED_FUNCTION_SIZE +} + +size_t WriteBarrierManager::GetCurrentWriteBarrierSize() +{ + return GetSpecificWriteBarrierSize(m_currentWriteBarrier); +} + +PBYTE WriteBarrierManager::CalculatePatchLocation(LPVOID base, LPVOID label, int offset) +{ + // the label should always come after the entrypoint for this funtion + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", (LPBYTE)label > (LPBYTE)base); + + return ((LPBYTE)GetEEFuncEntryPoint(JIT_WriteBarrier) + ((LPBYTE)GetEEFuncEntryPoint(label) - (LPBYTE)GetEEFuncEntryPoint(base) + offset)); +} + +void WriteBarrierManager::ChangeWriteBarrierTo(WriteBarrierType newWriteBarrier, bool isRuntimeSuspended) +{ + GCX_MAYBE_COOP_NO_THREAD_BROKEN((!isRuntimeSuspended && GetThread() != NULL)); + BOOL bEESuspendedHere = FALSE; + if(!isRuntimeSuspended && m_currentWriteBarrier != WRITE_BARRIER_UNINITIALIZED) + { + ThreadSuspend::SuspendEE(ThreadSuspend::SUSPEND_FOR_GC_PREP); + bEESuspendedHere = TRUE; + } + + _ASSERTE(m_currentWriteBarrier != newWriteBarrier); + m_currentWriteBarrier = newWriteBarrier; + + // the memcpy must come before the switch statment because the asserts inside the switch + // are actually looking into the JIT_WriteBarrier buffer + memcpy((PVOID)JIT_WriteBarrier, (LPVOID)GetCurrentWriteBarrierCode(), GetCurrentWriteBarrierSize()); + + switch (newWriteBarrier) + { + case WRITE_BARRIER_PREGROW64: + { + m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PreGrow64, Patch_Label_Lower, 2); + m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PreGrow64, Patch_Label_CardTable, 2); + + // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", 0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", 0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); + break; + } + + case WRITE_BARRIER_POSTGROW64: + { + m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PostGrow64, Patch_Label_Lower, 2); + m_pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PostGrow64, Patch_Label_Upper, 2); + m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PostGrow64, Patch_Label_CardTable, 2); + + // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", 0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", 0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", 0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pUpperBoundImmediate); + break; + } + +#ifdef FEATURE_SVR_GC + case WRITE_BARRIER_SVR64: + { + m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_SVR64, PatchLabel_CardTable, 2); + + // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", 0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); + break; + } +#endif // FEATURE_SVR_GC + +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + case WRITE_BARRIER_WRITE_WATCH_PREGROW64: + { + m_pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PreGrow64, Patch_Label_WriteWatchTable, 2); + m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PreGrow64, Patch_Label_Lower, 2); + m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PreGrow64, Patch_Label_CardTable, 2); + + // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", 0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pWriteWatchTableImmediate); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", 0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", 0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); + break; + } + + case WRITE_BARRIER_WRITE_WATCH_POSTGROW64: + { + m_pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_WriteWatchTable, 2); + m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_Lower, 2); + m_pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_Upper, 2); + m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_CardTable, 2); + + // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", 0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pWriteWatchTableImmediate); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", 0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", 0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", 0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pUpperBoundImmediate); + break; + } + +#ifdef FEATURE_SVR_GC + case WRITE_BARRIER_WRITE_WATCH_SVR64: + { + m_pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_SVR64, PatchLabel_WriteWatchTable, 2); + m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_SVR64, PatchLabel_CardTable, 2); + + // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", 0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pWriteWatchTableImmediate); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", 0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); + break; + } +#endif // FEATURE_SVR_GC +#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + + default: + UNREACHABLE_MSG("unexpected write barrier type!"); + } + + UpdateEphemeralBounds(true); + UpdateWriteWatchAndCardTableLocations(true, false); + + if(bEESuspendedHere) + { + ThreadSuspend::RestartEE(FALSE, TRUE); + } +} + +#undef CALC_PATCH_LOCATION + +void WriteBarrierManager::Initialize() +{ + CONTRACTL + { + MODE_ANY; + GC_NOTRIGGER; + NOTHROW; + } + CONTRACTL_END; + + + // Ensure that the generic JIT_WriteBarrier function buffer is large enough to hold any of the more specific + // write barrier implementations. + size_t cbWriteBarrierBuffer = GetSpecificWriteBarrierSize(WRITE_BARRIER_BUFFER); + + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", cbWriteBarrierBuffer >= GetSpecificWriteBarrierSize(WRITE_BARRIER_PREGROW64)); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", cbWriteBarrierBuffer >= GetSpecificWriteBarrierSize(WRITE_BARRIER_POSTGROW64)); +#ifdef FEATURE_SVR_GC + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", cbWriteBarrierBuffer >= GetSpecificWriteBarrierSize(WRITE_BARRIER_SVR64)); +#endif // FEATURE_SVR_GC +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", cbWriteBarrierBuffer >= GetSpecificWriteBarrierSize(WRITE_BARRIER_WRITE_WATCH_PREGROW64)); + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", cbWriteBarrierBuffer >= GetSpecificWriteBarrierSize(WRITE_BARRIER_WRITE_WATCH_POSTGROW64)); +#ifdef FEATURE_SVR_GC + _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", cbWriteBarrierBuffer >= GetSpecificWriteBarrierSize(WRITE_BARRIER_WRITE_WATCH_SVR64)); +#endif // FEATURE_SVR_GC +#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + +#if !defined(CODECOVERAGE) + Validate(); +#endif +} + +bool WriteBarrierManager::NeedDifferentWriteBarrier(bool bReqUpperBoundsCheck, WriteBarrierType* pNewWriteBarrierType) +{ + // Init code for the JIT_WriteBarrier assembly routine. Since it will be bashed everytime the GC Heap + // changes size, we want to do most of the work just once. + // + // The actual JIT_WriteBarrier routine will only be called in free builds, but we keep this code (that + // modifies it) around in debug builds to check that it works (with assertions). + + + WriteBarrierType writeBarrierType = m_currentWriteBarrier; + + for(;;) + { + switch (writeBarrierType) + { + case WRITE_BARRIER_UNINITIALIZED: +#ifdef _DEBUG + // Use the default slow write barrier some of the time in debug builds because of of contains some good asserts + if ((g_pConfig->GetHeapVerifyLevel() & EEConfig::HEAPVERIFY_BARRIERCHECK) || DbgRandomOnExe(0.5)) { + break; + } +#endif + + writeBarrierType = GCHeap::IsServerHeap() ? WRITE_BARRIER_SVR64 : WRITE_BARRIER_PREGROW64; + continue; + + case WRITE_BARRIER_PREGROW64: + if (bReqUpperBoundsCheck) + { + writeBarrierType = WRITE_BARRIER_POSTGROW64; + } + break; + + case WRITE_BARRIER_POSTGROW64: + break; + +#ifdef FEATURE_SVR_GC + case WRITE_BARRIER_SVR64: + break; +#endif // FEATURE_SVR_GC + +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + case WRITE_BARRIER_WRITE_WATCH_PREGROW64: + if (bReqUpperBoundsCheck) + { + writeBarrierType = WRITE_BARRIER_WRITE_WATCH_POSTGROW64; + } + break; + + case WRITE_BARRIER_WRITE_WATCH_POSTGROW64: + break; + +#ifdef FEATURE_SVR_GC + case WRITE_BARRIER_WRITE_WATCH_SVR64: + break; +#endif // FEATURE_SVR_GC +#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + + default: + UNREACHABLE_MSG("unexpected write barrier type!"); + } + break; + } + + *pNewWriteBarrierType = writeBarrierType; + return m_currentWriteBarrier != writeBarrierType; +} + +void WriteBarrierManager::UpdateEphemeralBounds(bool isRuntimeSuspended) +{ + bool needToFlushCache = false; + + WriteBarrierType newType; + if (NeedDifferentWriteBarrier(false, &newType)) + { + ChangeWriteBarrierTo(newType, isRuntimeSuspended); + return; + } + +#ifdef _DEBUG + // Using debug-only write barrier? + if (m_currentWriteBarrier == WRITE_BARRIER_UNINITIALIZED) + return; +#endif + + switch (m_currentWriteBarrier) + { + case WRITE_BARRIER_POSTGROW64: +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + case WRITE_BARRIER_WRITE_WATCH_POSTGROW64: +#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + { + // Change immediate if different from new g_ephermeral_high. + if (*(UINT64*)m_pUpperBoundImmediate != (size_t)g_ephemeral_high) + { + *(UINT64*)m_pUpperBoundImmediate = (size_t)g_ephemeral_high; + needToFlushCache = true; + } + } + // + // INTENTIONAL FALL-THROUGH! + // + case WRITE_BARRIER_PREGROW64: +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + case WRITE_BARRIER_WRITE_WATCH_PREGROW64: +#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + { + // Change immediate if different from new g_ephermeral_low. + if (*(UINT64*)m_pLowerBoundImmediate != (size_t)g_ephemeral_low) + { + *(UINT64*)m_pLowerBoundImmediate = (size_t)g_ephemeral_low; + needToFlushCache = true; + } + break; + } + +#ifdef FEATURE_SVR_GC + case WRITE_BARRIER_SVR64: +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + case WRITE_BARRIER_WRITE_WATCH_SVR64: +#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + { + break; + } +#endif // FEATURE_SVR_GC + + default: + UNREACHABLE_MSG("unexpected m_currentWriteBarrier in UpdateEphemeralBounds"); + } + + if (needToFlushCache) + { + FlushInstructionCache(GetCurrentProcess(), (PVOID)JIT_WriteBarrier, GetCurrentWriteBarrierSize()); + } +} + +void WriteBarrierManager::UpdateWriteWatchAndCardTableLocations(bool isRuntimeSuspended, bool bReqUpperBoundsCheck) +{ + // If we are told that we require an upper bounds check (GC did some heap + // reshuffling), we need to switch to the WriteBarrier_PostGrow function for + // good. + + WriteBarrierType newType; + if (NeedDifferentWriteBarrier(bReqUpperBoundsCheck, &newType)) + { + ChangeWriteBarrierTo(newType, isRuntimeSuspended); + return; + } + +#ifdef _DEBUG + // Using debug-only write barrier? + if (m_currentWriteBarrier == WRITE_BARRIER_UNINITIALIZED) + return; +#endif + + bool fFlushCache = false; + +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + switch (m_currentWriteBarrier) + { + case WRITE_BARRIER_WRITE_WATCH_PREGROW64: + case WRITE_BARRIER_WRITE_WATCH_POSTGROW64: +#ifdef FEATURE_SVR_GC + case WRITE_BARRIER_WRITE_WATCH_SVR64: +#endif // FEATURE_SVR_GC + if (*(UINT64*)m_pWriteWatchTableImmediate != (size_t)SoftwareWriteWatch::GetTable()) + { + *(UINT64*)m_pWriteWatchTableImmediate = (size_t)SoftwareWriteWatch::GetTable(); + fFlushCache = true; + } + break; + + default: + break; // clang seems to require all enum values to be covered for some reason + } +#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + + if (*(UINT64*)m_pCardTableImmediate != (size_t)g_card_table) + { + *(UINT64*)m_pCardTableImmediate = (size_t)g_card_table; + fFlushCache = true; + } + + if (fFlushCache) + { + FlushInstructionCache(GetCurrentProcess(), (LPVOID)JIT_WriteBarrier, GetCurrentWriteBarrierSize()); + } +} + +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP +void WriteBarrierManager::SwitchToWriteWatchBarrier(bool isRuntimeSuspended) +{ + WriteBarrierType newWriteBarrierType; + switch (m_currentWriteBarrier) + { + case WRITE_BARRIER_UNINITIALIZED: + // Using the debug-only write barrier + return; + + case WRITE_BARRIER_PREGROW64: + newWriteBarrierType = WRITE_BARRIER_WRITE_WATCH_PREGROW64; + break; + + case WRITE_BARRIER_POSTGROW64: + newWriteBarrierType = WRITE_BARRIER_WRITE_WATCH_POSTGROW64; + break; + +#ifdef FEATURE_SVR_GC + case WRITE_BARRIER_SVR64: + newWriteBarrierType = WRITE_BARRIER_WRITE_WATCH_SVR64; + break; +#endif // FEATURE_SVR_GC + + default: + UNREACHABLE(); + } + + ChangeWriteBarrierTo(newWriteBarrierType, isRuntimeSuspended); +} + +void WriteBarrierManager::SwitchToNonWriteWatchBarrier(bool isRuntimeSuspended) +{ + WriteBarrierType newWriteBarrierType; + switch (m_currentWriteBarrier) + { + case WRITE_BARRIER_UNINITIALIZED: + // Using the debug-only write barrier + return; + + case WRITE_BARRIER_WRITE_WATCH_PREGROW64: + newWriteBarrierType = WRITE_BARRIER_PREGROW64; + break; + + case WRITE_BARRIER_WRITE_WATCH_POSTGROW64: + newWriteBarrierType = WRITE_BARRIER_POSTGROW64; + break; + +#ifdef FEATURE_SVR_GC + case WRITE_BARRIER_WRITE_WATCH_SVR64: + newWriteBarrierType = WRITE_BARRIER_SVR64; + break; +#endif // FEATURE_SVR_GC + + default: + UNREACHABLE(); + } + + ChangeWriteBarrierTo(newWriteBarrierType, isRuntimeSuspended); +} +#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + +// This function bashes the super fast amd64 version of the JIT_WriteBarrier +// helper. It should be called by the GC whenever the ephermeral region +// bounds get changed, but still remain on the top of the GC Heap. +void StompWriteBarrierEphemeral(bool isRuntimeSuspended) +{ + WRAPPER_NO_CONTRACT; + + g_WriteBarrierManager.UpdateEphemeralBounds(isRuntimeSuspended); +} + +// This function bashes the super fast amd64 versions of the JIT_WriteBarrier +// helpers. It should be called by the GC whenever the ephermeral region gets moved +// from being at the top of the GC Heap, and/or when the cards table gets moved. +void StompWriteBarrierResize(bool isRuntimeSuspended, bool bReqUpperBoundsCheck) +{ + WRAPPER_NO_CONTRACT; + + g_WriteBarrierManager.UpdateWriteWatchAndCardTableLocations(isRuntimeSuspended, bReqUpperBoundsCheck); +} + +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP +void SwitchToWriteWatchBarrier(bool isRuntimeSuspended) +{ + WRAPPER_NO_CONTRACT; + + g_WriteBarrierManager.SwitchToWriteWatchBarrier(isRuntimeSuspended); +} + +void SwitchToNonWriteWatchBarrier(bool isRuntimeSuspended) +{ + WRAPPER_NO_CONTRACT; + + g_WriteBarrierManager.SwitchToNonWriteWatchBarrier(isRuntimeSuspended); +} +#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP diff --git a/src/vm/amd64/pinvokestubs.S b/src/vm/amd64/pinvokestubs.S new file mode 100644 index 0000000000..49697e1aad --- /dev/null +++ b/src/vm/amd64/pinvokestubs.S @@ -0,0 +1,129 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +.intel_syntax noprefix +#include "unixasmmacros.inc" +#include "asmconstants.h" + + +// +// in: +// PINVOKE_CALLI_TARGET_REGISTER (r10) = unmanaged target +// PINVOKE_CALLI_SIGTOKEN_REGNUM (r11) = sig token +// +// out: +// METHODDESC_REGISTER (r10) = unmanaged target +// +LEAF_ENTRY GenericPInvokeCalliHelper, _TEXT + + // + // check for existing IL stub + // + mov rax, [PINVOKE_CALLI_SIGTOKEN_REGISTER + OFFSETOF__VASigCookie__pNDirectILStub] + test rax, rax + jz C_FUNC(GenericPInvokeCalliGenILStub) + + // + // We need to distinguish between a MethodDesc* and an unmanaged target in PInvokeStubForHost(). + // The way we do this is to shift the managed target to the left by one bit and then set the + // least significant bit to 1. This works because MethodDesc* are always 8-byte aligned. + // + shl PINVOKE_CALLI_TARGET_REGISTER, 1 + or PINVOKE_CALLI_TARGET_REGISTER, 1 + + // + // jump to existing IL stub + // + jmp rax + +LEAF_END GenericPInvokeCalliHelper, _TEXT + +NESTED_ENTRY GenericPInvokeCalliGenILStub, _TEXT, NoHandler + + PROLOG_WITH_TRANSITION_BLOCK + + // + // save target + // + mov r12, METHODDESC_REGISTER + mov r13, PINVOKE_CALLI_SIGTOKEN_REGISTER + + // + // GenericPInvokeCalliStubWorker(TransitionBlock * pTransitionBlock, VASigCookie * pVASigCookie, PCODE pUnmanagedTarget) + // + lea rdi, [rsp + __PWTB_TransitionBlock] // pTransitionBlock* + mov rsi, PINVOKE_CALLI_SIGTOKEN_REGISTER // pVASigCookie + mov rdx, METHODDESC_REGISTER // pUnmanagedTarget + call C_FUNC(GenericPInvokeCalliStubWorker) + + // + // restore target + // + mov METHODDESC_REGISTER, r12 + mov PINVOKE_CALLI_SIGTOKEN_REGISTER, r13 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + jmp C_FUNC(GenericPInvokeCalliHelper) + +NESTED_END GenericPInvokeCalliGenILStub, _TEXT + +LEAF_ENTRY VarargPInvokeStub, _TEXT + mov PINVOKE_CALLI_SIGTOKEN_REGISTER, rdi + jmp C_FUNC(VarargPInvokeStubHelper) +LEAF_END VarargPInvokeStub, _TEXT + +LEAF_ENTRY VarargPInvokeStub_RetBuffArg, _TEXT + mov PINVOKE_CALLI_SIGTOKEN_REGISTER, rsi + jmp C_FUNC(VarargPInvokeStubHelper) +LEAF_END VarargPInvokeStub_RetBuffArg, _TEXT + +LEAF_ENTRY VarargPInvokeStubHelper, _TEXT + // + // check for existing IL stub + // + mov rax, [PINVOKE_CALLI_SIGTOKEN_REGISTER + OFFSETOF__VASigCookie__pNDirectILStub] + test rax, rax + jz C_FUNC(VarargPInvokeGenILStub) + + // + // jump to existing IL stub + // + jmp rax + +LEAF_END VarargPInvokeStubHelper, _TEXT + +// +// IN: METHODDESC_REGISTER (R10) stub secret param +// PINVOKE_CALLI_SIGTOKEN_REGISTER (R11) VASigCookie* +// +// ASSUMES: we already checked for an existing stub to use +// +NESTED_ENTRY VarargPInvokeGenILStub, _TEXT, NoHandler + + PROLOG_WITH_TRANSITION_BLOCK + + // + // save target + // + mov r12, METHODDESC_REGISTER + mov r13, PINVOKE_CALLI_SIGTOKEN_REGISTER + + // + // VarargPInvokeStubWorker(TransitionBlock * pTransitionBlock, VASigCookie *pVASigCookie, MethodDesc *pMD) + // + lea rdi, [rsp + __PWTB_TransitionBlock] // pTransitionBlock* + mov rsi, PINVOKE_CALLI_SIGTOKEN_REGISTER // pVASigCookie + mov rdx, METHODDESC_REGISTER // pMD + call C_FUNC(VarargPInvokeStubWorker) + + // + // restore target + // + mov METHODDESC_REGISTER, r12 + mov PINVOKE_CALLI_SIGTOKEN_REGISTER, r13 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + jmp C_FUNC(VarargPInvokeStubHelper) + +NESTED_END VarargPInvokeGenILStub, _TEXT diff --git a/src/vm/amd64/profiler.cpp b/src/vm/amd64/profiler.cpp new file mode 100644 index 0000000000..e88cbba9ee --- /dev/null +++ b/src/vm/amd64/profiler.cpp @@ -0,0 +1,367 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +// +// FILE: profiler.cpp +// + +// + +// +// ====================================================================================== + +#include "common.h" + +#ifdef PROFILING_SUPPORTED +#include "proftoeeinterfaceimpl.h" + +MethodDesc *FunctionIdToMethodDesc(FunctionID functionID); + +// TODO: move these to some common.h file +// FLAGS +#define PROFILE_ENTER 0x1 +#define PROFILE_LEAVE 0x2 +#define PROFILE_TAILCALL 0x4 + +typedef struct _PROFILE_PLATFORM_SPECIFIC_DATA +{ + FunctionID functionId; + void *rbp; + void *probeRsp; + void *ip; + void *profiledRsp; + UINT64 rax; + LPVOID hiddenArg; + UINT64 flt0; // floats stored as doubles + UINT64 flt1; + UINT64 flt2; + UINT64 flt3; + UINT32 flags; +} PROFILE_PLATFORM_SPECIFIC_DATA, *PPROFILE_PLATFORM_SPECIFIC_DATA; + + +/* + * ProfileGetIPFromPlatformSpecificHandle + * + * This routine takes the platformSpecificHandle and retrieves from it the + * IP value. + * + * Parameters: + * handle - the platformSpecificHandle passed to ProfileEnter/Leave/Tailcall + * + * Returns: + * The IP value stored in the handle. + */ +UINT_PTR ProfileGetIPFromPlatformSpecificHandle(void *handle) +{ + LIMITED_METHOD_CONTRACT; + + PROFILE_PLATFORM_SPECIFIC_DATA* pData = (PROFILE_PLATFORM_SPECIFIC_DATA*)handle; + return (UINT_PTR)pData->ip; +} + + +/* + * ProfileSetFunctionIDInPlatformSpecificHandle + * + * This routine takes the platformSpecificHandle and functionID, and assign + * functionID to functionID field of platformSpecificHandle. + * + * Parameters: + * pPlatformSpecificHandle - the platformSpecificHandle passed to ProfileEnter/Leave/Tailcall + * functionID - the FunctionID to be assigned + * + * Returns: + * None + */ +void ProfileSetFunctionIDInPlatformSpecificHandle(void * pPlatformSpecificHandle, FunctionID functionID) +{ + LIMITED_METHOD_CONTRACT; + _ASSERTE(pPlatformSpecificHandle != NULL); + _ASSERTE(functionID != NULL); + + PROFILE_PLATFORM_SPECIFIC_DATA * pData = reinterpret_cast(pPlatformSpecificHandle); + pData->functionId = functionID; +} + +/* + * ProfileArgIterator::ProfileArgIterator + * + * Constructor. Initializes for arg iteration. + * + * Parameters: + * pMetaSig - The signature of the method we are going iterate over + * platformSpecificHandle - the value passed to ProfileEnter/Leave/Tailcall + * + * Returns: + * None. + */ +ProfileArgIterator::ProfileArgIterator(MetaSig * pSig, void * platformSpecificHandle) : + m_argIterator(pSig) +{ + WRAPPER_NO_CONTRACT; + + _ASSERTE(pSig != NULL); + _ASSERTE(platformSpecificHandle != NULL); + + m_handle = platformSpecificHandle; + PROFILE_PLATFORM_SPECIFIC_DATA* pData = (PROFILE_PLATFORM_SPECIFIC_DATA*)m_handle; + + // unwind a frame and get the Rsp for the profiled method to make sure it matches + // what the JIT gave us +#ifdef _DEBUG + { + // setup the context to represent the frame that called ProfileEnterNaked + CONTEXT ctx; + memset(&ctx, 0, sizeof(CONTEXT)); + ctx.Rsp = (UINT64)pData->probeRsp; + ctx.Rbp = (UINT64)pData->rbp; + ctx.Rip = (UINT64)pData->ip; + + // walk up a frame to the caller frame (called the managed method which + // called ProfileEnterNaked) + Thread::VirtualUnwindCallFrame(&ctx); + + _ASSERTE(pData->profiledRsp == (void*)ctx.Rsp); + } +#endif // _DEBUG + + // Get the hidden arg if there is one + MethodDesc * pMD = FunctionIdToMethodDesc(pData->functionId); + + if ( (pData->hiddenArg == NULL) && + (pMD->RequiresInstArg() || pMD->AcquiresInstMethodTableFromThis()) ) + { + // In the enter probe, the JIT may not have pushed the generics token onto the stack yet. + // Luckily, we can inspect the registers reliably at this point. + if (pData->flags & PROFILE_ENTER) + { + _ASSERTE(!((pData->flags & PROFILE_LEAVE) || (pData->flags & PROFILE_TAILCALL))); + + if (pMD->AcquiresInstMethodTableFromThis()) + { + pData->hiddenArg = GetThis(); + } + else + { + // The param type arg comes after the return buffer argument and the "this" pointer. + int index = 0; + + if (m_argIterator.HasThis()) + { + index++; + } + + if (m_argIterator.HasRetBuffArg()) + { + index++; + } + + pData->hiddenArg = *(LPVOID*)((LPBYTE)pData->profiledRsp + (index * sizeof(SIZE_T))); + } + } + else + { + EECodeInfo codeInfo((PCODE)pData->ip); + + // We want to pass the caller SP here. + pData->hiddenArg = EECodeManager::GetExactGenericsToken((SIZE_T)(pData->profiledRsp), &codeInfo); + } + } +} + +/* + * ProfileArgIterator::~ProfileArgIterator + * + * Destructor, releases all resources. + * + */ +ProfileArgIterator::~ProfileArgIterator() +{ + LIMITED_METHOD_CONTRACT; + + m_handle = NULL; +} + +/* + * ProfileArgIterator::GetNextArgAddr + * + * After initialization, this method is called repeatedly until it + * returns NULL to get the address of each arg. Note: this address + * could be anywhere on the stack. + * + * Returns: + * Address of the argument, or NULL if iteration is complete. + */ +LPVOID ProfileArgIterator::GetNextArgAddr() +{ + WRAPPER_NO_CONTRACT; + + _ASSERTE(m_handle != NULL); + + PROFILE_PLATFORM_SPECIFIC_DATA* pData = (PROFILE_PLATFORM_SPECIFIC_DATA*)m_handle; + + if ((pData->flags & PROFILE_LEAVE) || (pData->flags & PROFILE_TAILCALL)) + { + _ASSERTE(!"GetNextArgAddr() - arguments are not available in leave and tailcall probes"); + return NULL; + } + + int argOffset = m_argIterator.GetNextOffset(); + + // argOffset of TransitionBlock::InvalidOffset indicates that we're done + if (argOffset == TransitionBlock::InvalidOffset) + { + return NULL; + } + + // stack args are offset against the profiledRsp + if (TransitionBlock::IsStackArgumentOffset(argOffset)) + { + LPVOID pArg = ((LPBYTE)pData->profiledRsp) + (argOffset - TransitionBlock::GetOffsetOfArgs()); + + if (m_argIterator.IsArgPassedByRef()) + pArg = *(LPVOID *)pArg; + + return pArg; + } + + // if we're here we have an enregistered argument + int regStructOfs = (argOffset - TransitionBlock::GetOffsetOfArgumentRegisters()); + _ASSERTE(regStructOfs < ARGUMENTREGISTERS_SIZE); + + CorElementType t = m_argIterator.GetArgType(); + _ASSERTE(IS_ALIGNED(regStructOfs, sizeof(SLOT))); + if (t == ELEMENT_TYPE_R4 || t == ELEMENT_TYPE_R8) + { + return (LPBYTE)&pData->flt0 + regStructOfs; + } + else + { + // enregistered args (which are really stack homed) are offset against profiledRsp + LPVOID pArg = ((LPBYTE)pData->profiledRsp + regStructOfs); + + if (m_argIterator.IsArgPassedByRef()) + pArg = *(LPVOID *)pArg; + + return pArg; + } + + return NULL; +} + +/* + * ProfileArgIterator::GetHiddenArgValue + * + * Called after initialization, any number of times, to retrieve any + * hidden argument, so that resolution for Generics can be done. + * + * Parameters: + * None. + * + * Returns: + * Value of the hidden parameter, or NULL if none exists. + */ +LPVOID ProfileArgIterator::GetHiddenArgValue(void) +{ + LIMITED_METHOD_CONTRACT; + + PROFILE_PLATFORM_SPECIFIC_DATA* pData = (PROFILE_PLATFORM_SPECIFIC_DATA*)m_handle; + + return pData->hiddenArg; +} + +/* + * ProfileArgIterator::GetThis + * + * Called after initialization, any number of times, to retrieve any + * 'this' pointer. + * + * Parameters: + * None. + * + * Returns: + * Address of the 'this', or NULL if none exists. + */ +LPVOID ProfileArgIterator::GetThis(void) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + PROFILE_PLATFORM_SPECIFIC_DATA* pData = (PROFILE_PLATFORM_SPECIFIC_DATA*)m_handle; + MethodDesc * pMD = FunctionIdToMethodDesc(pData->functionId); + + // We guarantee to return the correct "this" pointer in the enter probe. + // For the leave and tailcall probes, we only return a valid "this" pointer if it is the generics token. + if (pData->hiddenArg != NULL) + { + if (pMD->AcquiresInstMethodTableFromThis()) + { + return pData->hiddenArg; + } + } + + if (pData->flags & PROFILE_ENTER) + { + if (m_argIterator.HasThis()) + { + return *(LPVOID*)((LPBYTE)pData->profiledRsp); + } + } + + return NULL; +} + +/* + * ProfileArgIterator::GetReturnBufferAddr + * + * Called after initialization, any number of times, to retrieve the + * address of the return buffer. NULL indicates no return value. + * + * Parameters: + * None. + * + * Returns: + * Address of the return buffer, or NULL if none exists. + */ +LPVOID ProfileArgIterator::GetReturnBufferAddr(void) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + PROFILE_PLATFORM_SPECIFIC_DATA* pData = (PROFILE_PLATFORM_SPECIFIC_DATA*)m_handle; + + if (m_argIterator.HasRetBuffArg()) + { + // the JIT64 makes sure that in ret-buf-arg cases where the method is being profiled that + // rax is setup with the address of caller passed in buffer. this is _questionably_ required + // by our calling convention, but is required by our profiler spec. + return (LPVOID)pData->rax; + } + + CorElementType t = m_argIterator.GetSig()->GetReturnType(); + if (ELEMENT_TYPE_VOID != t) + { + if (ELEMENT_TYPE_R4 == t || ELEMENT_TYPE_R8 == t) + pData->rax = pData->flt0; + + return &(pData->rax); + } + else + return NULL; +} + +#undef PROFILE_ENTER +#undef PROFILE_LEAVE +#undef PROFILE_TAILCALL + +#endif // PROFILING_SUPPORTED + diff --git a/src/vm/amd64/remotingamd64.cpp b/src/vm/amd64/remotingamd64.cpp new file mode 100644 index 0000000000..587afae124 --- /dev/null +++ b/src/vm/amd64/remotingamd64.cpp @@ -0,0 +1,672 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +/*=========================================================================== +** +** File: RemotingCpu.cpp +** +** +** +** Purpose: Defines various remoting related functions for the AMD64 architecture +** +** +** See code:EEStartup#TableOfContents for EE overview +** +=============================================================================*/ + +#include "common.h" + +#ifdef FEATURE_REMOTING + +#include "excep.h" +#include "comdelegate.h" +#include "remoting.h" +#include "field.h" +#include "siginfo.hpp" +#include "stackbuildersink.h" +#include "threads.h" +#include "method.hpp" + +#include "asmconstants.h" + +// External variables +extern DWORD g_dwNonVirtualThunkRemotingLabelOffset; +extern DWORD g_dwNonVirtualThunkReCheckLabelOffset; + +//+---------------------------------------------------------------------------- +// +// Method: CRemotingServices::CheckForContextMatch public +// +// Synopsis: This code generates a check to see if the current context and +// the context of the proxy match. +// +//+---------------------------------------------------------------------------- +// +// returns zero if contexts match +// returns non-zero if contexts don't match +// +extern "C" UINT_PTR __stdcall CRemotingServices__CheckForContextMatch(Object* pStubData) +{ + // This method cannot have a contract because CreateStubForNonVirtualMethod assumes + // it won't trash XMM registers. The code generated for contracts by recent compilers + // is trashing XMM registers. + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_MODE_COOPERATIVE; // due to the Object parameter + STATIC_CONTRACT_SO_TOLERANT; + + UINT_PTR contextID = *(UINT_PTR*)pStubData->UnBox(); + UINT_PTR contextCur = (UINT_PTR)GetThread()->m_Context; + return (contextCur != contextID); // chosen to match x86 convention +} + + +//+---------------------------------------------------------------------------- +// +// Method: CTPMethodTable::CreateThunkForVirtualMethod private +// +// Synopsis: Creates the thunk that pushes the supplied slot number and jumps +// to TP Stub +// +//+---------------------------------------------------------------------------- +PCODE CTPMethodTable::CreateThunkForVirtualMethod(DWORD dwSlot, BYTE* pbCode) +{ + LIMITED_METHOD_CONTRACT; + + BYTE *pbCodeStart = pbCode; + + // NOTE: if you change the code generated here, update + // CVirtualThunkMgr::IsThunkByASM, CVirtualThunkMgr::GetMethodDescByASM + + // + // mov r10, + // mov rax, TransparentProxyStub + // jmp rax + // + *pbCode++ = 0x49; + *pbCode++ = 0xc7; + *pbCode++ = 0xc2; + *((DWORD*)pbCode) = dwSlot; + pbCode += sizeof(DWORD); + *pbCode++ = 0x48; + *pbCode++ = 0xB8; + *((UINT64*)pbCode) = (UINT64)(TransparentProxyStub); + pbCode += sizeof(UINT64); + *pbCode++ = 0xFF; + *pbCode++ = 0xE0; + + _ASSERTE(pbCode - pbCodeStart == ConstVirtualThunkSize); + _ASSERTE(CVirtualThunkMgr::IsThunkByASM((PCODE)pbCodeStart)); + + return (PCODE)pbCodeStart; +} + + +#ifdef HAS_REMOTING_PRECODE + +//+---------------------------------------------------------------------------- +// +// Method: CTPMethodTable::ActivatePrecodeRemotingThunk private +// +// Synopsis: Patch the precode remoting thunk to begin interception +// +//+---------------------------------------------------------------------------- +void CTPMethodTable::ActivatePrecodeRemotingThunk() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + PORTABILITY_WARNING("CTPMethodTable::ActivatePrecodeRemotingThunk"); +} + +#else // HAS_REMOTING_PRECODE + +//+---------------------------------------------------------------------------- +// +// Method: CTPMethodTable::CreateStubForNonVirtualMethod public +// +// Synopsis: Create a stub for a non virtual method +// +//+---------------------------------------------------------------------------- +Stub* CTPMethodTable::CreateStubForNonVirtualMethod(MethodDesc* pMD, CPUSTUBLINKER* psl, + LPVOID pvAddrOfCode, Stub* pInnerStub) +{ + STANDARD_VM_CONTRACT; + + // Sanity check + + Stub *pStub = NULL; + + // we need a hash table only for virtual methods + _ASSERTE(!pMD->IsVirtual()); + + // Ensure the TP MethodTable's fields have been initialized. + EnsureFieldsInitialized(); + + /* + NonVirtualMethodStub + { + ;; thisReg: this + + sub rsp, 0x28 + + test thisReg, thisReg + je JmpAddrLabel + + mov rax, [thisReg] + mov r10, + cmp rax, r10 + jne JmpAddrLabel + + mov [rsp+0x30], rcx ;| + mov [rsp+0x38], rdx ;| + mov [rsp+0x40], r8 ;| + mov [rsp+0x48], r9 ;| + ;| + mov rax, [thisReg + TransparentProxyObject___stubData] ;| + call [thisReg + TransparentProxyObject___stub] ;| EmitCallToStub + ;| + mov rcx, [rsp+0x30] ;| + mov rdx, [rsp+0x38] ;| + mov r8, [rsp+0x40] ;| + mov r9, [rsp+0x48] ;| + ;| + test rax, rax ;| + jnz RemotingLabel ;| + + JmpAddrLabel: + mov rax, + add rsp, 0x28 + jmp rax + + RemotingLabel: + mov r10, + mov rax, + add rsp, 0x20 + jmp rax + } + */ + + X86Reg thisReg = kRCX; + void* pvTPStub = TransparentProxyStub_CrossContext; + + // Generate label where a null reference exception will be thrown + CodeLabel *pJmpAddrLabel = psl->NewCodeLabel(); + // Generate label where remoting code will execute + CodeLabel *pRemotingLabel = psl->NewCodeLabel(); + + // NOTE: if you change any of this code, you must update + // CNonVirtualThunkMgr::IsThunkByASM. + + // Allocate callee scratch area + // sub rsp, 0x28 + psl->X86EmitSubEsp(0x28); + + // test thisReg, thisReg + psl->X86EmitR2ROp(0x85, thisReg, thisReg); + // je JmpAddrLabel + psl->X86EmitCondJump(pJmpAddrLabel, X86CondCode::kJE); + + // Emit a label here for the debugger. A breakpoint will + // be set at the next instruction and the debugger will + // call CNonVirtualThunkMgr::TraceManager when the + // breakpoint is hit with the thread's context. + CodeLabel *pRecheckLabel = psl->NewCodeLabel(); + psl->EmitLabel(pRecheckLabel); + + // mov rax, [thisReg] + psl->X86EmitIndexRegLoad(kRAX, thisReg, 0); + + // mov r10, CTPMethodTable::GetMethodTable() + psl->X86EmitRegLoad(kR10, (UINT_PTR)CTPMethodTable::GetMethodTable()); + // cmp rax, r10 + psl->X86EmitR2ROp(0x3B, kRAX, kR10); + + // jne JmpAddrLabel + psl->X86EmitCondJump(pJmpAddrLabel, X86CondCode::kJNE); + + // CONSIDER: write all possible stubs in asm to ensure param registers are not trashed + + // mov [rsp+0x30], rcx + // mov [rsp+0x38], rdx + // mov [rsp+0x40], r8 + // mov [rsp+0x48], r9 + psl->X86EmitRegSave(kRCX, 0x30); + psl->X86EmitRegSave(kRDX, 0x38); + psl->X86EmitRegSave(kR8, 0x40); + psl->X86EmitRegSave(kR9, 0x48); + + // mov rax, [thisReg + TransparentProxyObject___stub] + psl->X86EmitIndexRegLoad(kRAX, thisReg, TransparentProxyObject___stub); + + // mov rcx, [thisReg + TransparentProxyObject___stubData] + psl->X86EmitIndexRegLoad(kRCX, thisReg, TransparentProxyObject___stubData); + + // call rax + psl->Emit16(0xd0ff); + + // mov rcx, [rsp+0x30] + // mov rdx, [rsp+0x38] + // mov r8, [rsp+0x40] + // mov r9, [rsp+0x48] + psl->X86EmitEspOffset(0x8b, kRCX, 0x30); + psl->X86EmitEspOffset(0x8b, kRDX, 0x38); + psl->X86EmitEspOffset(0x8b, kR8, 0x40); + psl->X86EmitEspOffset(0x8b, kR9, 0x48); + + // test rax, rax + psl->X86EmitR2ROp(0x85, kRAX, kRAX); + // jnz RemotingLabel + psl->X86EmitCondJump(pRemotingLabel, X86CondCode::kJNZ); + +// pJmpAddrLabel: + psl->EmitLabel(pJmpAddrLabel); + + // Make sure that the actual code does not require MethodDesc in r10 + _ASSERTE(!pMD->RequiresMethodDescCallingConvention()); + + // mov rax, + // add rsp, 0x28 + // REX.W jmp rax + psl->X86EmitTailcallWithESPAdjust(psl->NewExternalCodeLabel(pvAddrOfCode), 0x28); + +// pRemotingLabel: + psl->EmitLabel(pRemotingLabel); + + // mov r10, + psl->X86EmitRegLoad(kR10, (UINT_PTR)pMD); + + // mov rax, + // add rsp, 0x28 + // REX.W jmp rax + psl->X86EmitTailcallWithESPAdjust(psl->NewExternalCodeLabel(pvTPStub), 0x28); + + // Link and produce the stub + pStub = psl->LinkInterceptor(pMD->GetLoaderAllocator()->GetStubHeap(), + pInnerStub, pvAddrOfCode); + + return pStub; +} + + +//+---------------------------------------------------------------------------- +// +// Synopsis: Find an existing thunk or create a new one for the given +// method descriptor. NOTE: This is used for the methods that do +// not go through the vtable such as constructors, private and +// final methods. +// +//+---------------------------------------------------------------------------- +PCODE CTPMethodTable::CreateNonVirtualThunkForVirtualMethod(MethodDesc* pMD) +{ + CONTRACTL + { + STANDARD_VM_CHECK; + PRECONDITION(CheckPointer(pMD)); + } + CONTRACTL_END; + + CPUSTUBLINKER sl; + CPUSTUBLINKER* psl = &sl; + + Stub *pStub = NULL; + + // The thunk has not been created yet. Go ahead and create it. + // Compute the address of the slot + LPVOID pvEntryPoint = (LPVOID)pMD->GetMethodEntryPoint(); + + X86Reg thisReg = kRCX; + void* pvStub = CRemotingServices__DispatchInterfaceCall; + + // Generate label where a null reference exception will be thrown + CodeLabel *pExceptionLabel = psl->NewCodeLabel(); + + // !!! WARNING WARNING WARNING WARNING WARNING !!! + // + // DO NOT CHANGE this code without changing the thunk recognition + // code in CNonVirtualThunkMgr::IsThunkByASM + // & CNonVirtualThunkMgr::GetMethodDescByASM + // + // !!! WARNING WARNING WARNING WARNING WARNING !!! + + // NOTE: constant mov's should use an extended register to force a REX + // prefix and the full 64-bit immediate value, so that + // g_dwNonVirtualThunkRemotingLabelOffset and + // g_dwNonVirtualThunkReCheckLabelOffset are the same for all + // generated code. + + // if this == NULL throw NullReferenceException + // test rcx, rcx + psl->X86EmitR2ROp(0x85, thisReg, thisReg); + + // je ExceptionLabel + psl->X86EmitCondJump(pExceptionLabel, X86CondCode::kJE); + + // Generate label where remoting code will execute + CodeLabel *pRemotingLabel = psl->NewCodeLabel(); + + // Emit a label here for the debugger. A breakpoint will + // be set at the next instruction and the debugger will + // call CNonVirtualThunkMgr::TraceManager when the + // breakpoint is hit with the thread's context. + CodeLabel *pRecheckLabel = psl->NewCodeLabel(); + psl->EmitLabel(pRecheckLabel); + + // If this.MethodTable == TPMethodTable then do RemotingCall + // mov rax, [thisReg] + psl->X86EmitIndexRegLoad(kRAX, thisReg, 0); + // mov r10, CTPMethodTable::GetMethodTable() + psl->X86EmitRegLoad(kR10, (UINT_PTR)CTPMethodTable::GetMethodTable()); + // cmp rax, r10 + psl->X86EmitR2ROp(0x3B, kRAX, kR10); + // je RemotingLabel + psl->X86EmitCondJump(pRemotingLabel, X86CondCode::kJE); + + // Exception handling and non-remoting share the + // same codepath + psl->EmitLabel(pExceptionLabel); + + // Non-RemotingCode + // Jump to the vtable slot of the method + // mov rax, pvEntryPoint + // Encoded the mov manually so that it always uses the 64-bit form. + //psl->X86EmitRegLoad(kRAX, (UINT_PTR)pvEntryPoint); + psl->Emit8(REX_PREFIX_BASE | REX_OPERAND_SIZE_64BIT); + psl->Emit8(0xb8); + psl->EmitBytes((BYTE*)&pvEntryPoint, 8); + // jmp rax + psl->Emit8(0xff); + psl->Emit8(0xe0); + + // Remoting code. Note: CNonVirtualThunkMgr::TraceManager + // relies on this label being right after the jmp pvEntryPoint + // instruction above. If you move this label, update + // CNonVirtualThunkMgr::DoTraceStub. + psl->EmitLabel(pRemotingLabel); + + // Save the MethodDesc and goto TPStub + // push MethodDesc + psl->X86EmitRegLoad(kR10, (UINT_PTR)pMD); + + // jmp TPStub + psl->X86EmitNearJump(psl->NewExternalCodeLabel(pvStub)); + + // Link and produce the stub + // FUTURE: Do we have to provide the loader heap ? + pStub = psl->Link(SystemDomain::GetGlobalLoaderAllocator()->GetExecutableHeap()); + + // Grab the offset of the RemotingLabel and RecheckLabel + // for use in CNonVirtualThunkMgr::DoTraceStub and + // TraceManager. + DWORD dwOffset; + + dwOffset = psl->GetLabelOffset(pRemotingLabel); + ASSERT(!g_dwNonVirtualThunkRemotingLabelOffset || g_dwNonVirtualThunkRemotingLabelOffset == dwOffset); + g_dwNonVirtualThunkRemotingLabelOffset = dwOffset; + + dwOffset = psl->GetLabelOffset(pRecheckLabel); + ASSERT(!g_dwNonVirtualThunkReCheckLabelOffset || g_dwNonVirtualThunkReCheckLabelOffset == dwOffset); + g_dwNonVirtualThunkReCheckLabelOffset = dwOffset; + + return (pStub->GetEntryPoint()); +} + +#endif // HAS_REMOTING_PRECODE + +//+---------------------------------------------------------------------------- +// +// Method: CVirtualThunkMgr::DoTraceStub public +// +// Synopsis: Traces the stub given the starting address +// +//+---------------------------------------------------------------------------- +BOOL CVirtualThunkMgr::DoTraceStub(PCODE stubStartAddress, TraceDestination *trace) +{ + LIMITED_METHOD_CONTRACT; + + // implement this + return FALSE; +} + +//+---------------------------------------------------------------------------- +// +// Method: CVirtualThunkMgr::IsThunkByASM public +// +// Synopsis: Check assembly to see if this one of our thunks +// +//+---------------------------------------------------------------------------- +BOOL CVirtualThunkMgr::IsThunkByASM(PCODE startaddr) +{ + LIMITED_METHOD_CONTRACT; + + PTR_BYTE pbCode = PTR_BYTE(startaddr); + + // NOTE: this depends on the code generated by + // CTPMethodTable::CreateThunkForVirtualMethod. + + // mov r10, + return 0x49 == pbCode[0] + && 0xc7 == pbCode[1] + && 0xc2 == pbCode[2] + // mov rax, TransparentProxyStub + && 0x48 == pbCode[7] + && 0xb8 == pbCode[8] + && (TADDR)TransparentProxyStub == *PTR_TADDR(pbCode+9) + // jmp rax + && 0xff == pbCode[17] + && 0xe0 == pbCode[18]; +} + +//+---------------------------------------------------------------------------- +// +// Method: CVirtualThunkMgr::GetMethodDescByASM public +// +// Synopsis: Parses MethodDesc out of assembly code +// +//+---------------------------------------------------------------------------- +MethodDesc *CVirtualThunkMgr::GetMethodDescByASM(PCODE pbThunkCode, MethodTable *pMT) +{ + LIMITED_METHOD_CONTRACT; + + // NOTE: this depends on the code generated by + // CTPMethodTable::CreateThunkForVirtualMethod. + + return pMT->GetMethodDescForSlot(*((DWORD *) (pbThunkCode + 3))); +} + + +#ifndef HAS_REMOTING_PRECODE + +//+---------------------------------------------------------------------------- +// +// Method: CNonVirtualThunkMgr::TraceManager public +// +// Synopsis: Traces the stub given the current context +// +//+---------------------------------------------------------------------------- +BOOL CNonVirtualThunkMgr::TraceManager(Thread* thread, + TraceDestination* trace, + CONTEXT* pContext, + BYTE** pRetAddr) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(CheckPointer(thread, NULL_OK)); + PRECONDITION(CheckPointer(trace)); + PRECONDITION(CheckPointer(pContext)); + PRECONDITION(CheckPointer(pRetAddr)); + } + CONTRACTL_END; + + BOOL bRet = FALSE; + + MethodDesc * pMD = GetMethodDescByASM(GetIP(pContext) - g_dwNonVirtualThunkReCheckLabelOffset); + + LPBYTE pThis = (LPBYTE)pContext->Rcx; + + if ((pThis != NULL) && + (*(LPBYTE*)(SIZE_T)pThis == (LPBYTE)(SIZE_T)CTPMethodTable::GetMethodTable())) + { + // We know that we've got a proxy + // in the way. If the proxy is to a remote call, with no + // managed code in between, then the debugger doesn't care and + // we should just be able to return FALSE. + // + // + bRet = FALSE; + } + else + { + // No proxy in the way, so figure out where we're really going + // to and let the stub manager try to pickup the trace from + // there. + LPBYTE stubStartAddress = (LPBYTE)GetIP(pContext) - + g_dwNonVirtualThunkReCheckLabelOffset; + + // Extract the address of the destination + BYTE* pbAddr = (BYTE *)(SIZE_T)(stubStartAddress + + g_dwNonVirtualThunkRemotingLabelOffset - 2 - sizeof(void *)); + + SIZE_T destAddress = *(SIZE_T *)pbAddr; + + // Ask the stub manager to trace the destination address + bRet = StubManager::TraceStub((PCODE)(BYTE *)(size_t)destAddress, trace); + } + + // While we may have made it this far, further tracing may reveal + // that the debugger can't continue on. Therefore, since there is + // no frame currently pushed, we need to tell the debugger where + // we're returning to just in case it hits such a situtation. We + // know that the return address is on the top of the thread's + // stack. + (*pRetAddr) = *((BYTE**)(size_t)(GetSP(pContext))); + + return bRet; +} + +//+---------------------------------------------------------------------------- +// +// Method: CNonVirtualThunkMgr::DoTraceStub public +// +// Synopsis: Traces the stub given the starting address +// +//+---------------------------------------------------------------------------- +BOOL CNonVirtualThunkMgr::DoTraceStub(PCODE stubStartAddress, + TraceDestination* trace) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(stubStartAddress != NULL); + PRECONDITION(CheckPointer(trace)); + } + CONTRACTL_END; + + BOOL bRet = FALSE; + + if (!IsThunkByASM(stubStartAddress)) + return FALSE; + + CNonVirtualThunk* pThunk = FindThunk((const BYTE *)stubStartAddress); + + if(NULL != pThunk) + { + // We can either jump to + // (1) a slot in the transparent proxy table (UNMANAGED) + // (2) a slot in the non virtual part of the vtable + // ... so, we need to return TRACE_MGR_PUSH with the address + // at which we want to be called back with the thread's context + // so we can figure out which way we're gonna go. + if((const BYTE *)stubStartAddress == pThunk->GetThunkCode()) + { + trace->InitForManagerPush( + (PCODE) (stubStartAddress + g_dwNonVirtualThunkReCheckLabelOffset), + this); + bRet = TRUE; + } + } + + return bRet; +} + +//+---------------------------------------------------------------------------- +// +// Method: CNonVirtualThunkMgr::IsThunkByASM public +// +// Synopsis: Check assembly to see if this one of our thunks +// +//+---------------------------------------------------------------------------- +BOOL CNonVirtualThunkMgr::IsThunkByASM(PCODE startaddr) +{ + LIMITED_METHOD_CONTRACT; + + PTR_BYTE pbCode = PTR_BYTE(startaddr); + + // test rcx, rcx ; 3 bytes + return 0x48 == pbCode[0] + && 0x85 == pbCode[1] + && 0xc9 == pbCode[2] + // je ... ; 2 bytes + && 0x74 == pbCode[3] + // mov rax, [rcx] ; 3 bytes + // mov r10, CTPMethodTable::GetMethodTable() ; 2 bytes + MethodTable* + && (TADDR)CTPMethodTable::GetMethodTable() == *PTR_TADDR(pbCode + 10); +} + +//+---------------------------------------------------------------------------- +// +// Method: CNonVirtualThunkMgr::GetMethodDescByASM public +// +// Synopsis: Parses MethodDesc out of assembly code +// +//+---------------------------------------------------------------------------- +MethodDesc* CNonVirtualThunkMgr::GetMethodDescByASM(PCODE pbThunkCode) +{ + LIMITED_METHOD_CONTRACT; + + return *((MethodDesc **) (pbThunkCode + g_dwNonVirtualThunkRemotingLabelOffset + 2)); +} + +#endif // HAS_REMOTING_PRECODE + + +//+---------------------------------------------------------------------------- +// +// Method: CTPMethodTable::GenericCheckForContextMatch private +// +// Synopsis: Calls the stub in the TP & returns TRUE if the contexts +// match, FALSE otherwise. +// +// Note: 1. Called during FieldSet/Get, used for proxy extensibility +// +//+---------------------------------------------------------------------------- +BOOL __stdcall CTPMethodTable__GenericCheckForContextMatch(Object* orTP) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; // due to the Object parameter + SO_TOLERANT; + } + CONTRACTL_END; + + Object *StubData = OBJECTREFToObject(((TransparentProxyObject*)orTP)->GetStubData()); + CTPMethodTable::CheckContextCrossingProc *pfnCheckContextCrossing = + (CTPMethodTable::CheckContextCrossingProc*)(((TransparentProxyObject*)orTP)->GetStub()); + return pfnCheckContextCrossing(StubData) == 0; +} + +#endif // FEATURE_REMOTING + + diff --git a/src/vm/amd64/stublinkeramd64.cpp b/src/vm/amd64/stublinkeramd64.cpp new file mode 100644 index 0000000000..71213e09c5 --- /dev/null +++ b/src/vm/amd64/stublinkeramd64.cpp @@ -0,0 +1,8 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +#include "common.h" +#include "asmconstants.h" + +#include "../i386/stublinkerx86.cpp" + diff --git a/src/vm/amd64/stublinkeramd64.h b/src/vm/amd64/stublinkeramd64.h new file mode 100644 index 0000000000..8f006b823e --- /dev/null +++ b/src/vm/amd64/stublinkeramd64.h @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#ifndef _STUBLINKERAMD64_H_ +#define _STUBLINKERAMD64_H_ + +#include "../i386/stublinkerx86.h" + +#endif // _STUBLINKERAMD64_H_ diff --git a/src/vm/amd64/theprestubamd64.S b/src/vm/amd64/theprestubamd64.S new file mode 100644 index 0000000000..ff8d836b9a --- /dev/null +++ b/src/vm/amd64/theprestubamd64.S @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +.intel_syntax noprefix +#include "unixasmmacros.inc" +#include "asmconstants.h" + +NESTED_ENTRY ThePreStub, _TEXT, NoHandler + PROLOG_WITH_TRANSITION_BLOCK 0, 0, 0, 0, 0 + + // + // call PreStubWorker + // + lea rdi, [rsp + __PWTB_TransitionBlock] // pTransitionBlock* + mov rsi, METHODDESC_REGISTER + call C_FUNC(PreStubWorker) + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + TAILJMP_RAX + +NESTED_END ThePreStub, _TEXT + +LEAF_ENTRY ThePreStubPatch, _TEXT + // make sure that the basic block is unique + test eax,34 +PATCH_LABEL ThePreStubPatchLabel + ret +LEAF_END ThePreStubPatch, _TEXT + diff --git a/src/vm/amd64/umthunkstub.S b/src/vm/amd64/umthunkstub.S new file mode 100644 index 0000000000..e388f15490 --- /dev/null +++ b/src/vm/amd64/umthunkstub.S @@ -0,0 +1,210 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +.intel_syntax noprefix +#include "unixasmmacros.inc" +#include "asmconstants.h" + +// +// METHODDESC_REGISTER: UMEntryThunk* +// +NESTED_ENTRY TheUMEntryPrestub, _TEXT, UnhandledExceptionHandlerUnix + PUSH_ARGUMENT_REGISTERS + // +8 for alignment + alloc_stack (SIZEOF_MAX_FP_ARG_SPILL + 8) + SAVE_FLOAT_ARGUMENT_REGISTERS 0 + END_PROLOGUE + + mov rdi, METHODDESC_REGISTER + call C_FUNC(TheUMEntryPrestubWorker) + + // we're going to tail call to the exec stub that we just setup + + RESTORE_FLOAT_ARGUMENT_REGISTERS 0 + free_stack (SIZEOF_MAX_FP_ARG_SPILL + 8) + POP_ARGUMENT_REGISTERS + TAILJMP_RAX + +NESTED_END TheUMEntryPrestub, _TEXT + +// +// METHODDESC_REGISTER: UMEntryThunk* +// +NESTED_ENTRY UMThunkStub, _TEXT, UnhandledExceptionHandlerUnix +#define UMThunkStubAMD64_FIXED_STACK_ALLOC_SIZE (SIZEOF_MAX_INT_ARG_SPILL + SIZEOF_MAX_FP_ARG_SPILL + 0x8) +#define UMThunkStubAMD64_XMM_SAVE_OFFSET 0x0 +#define UMThunkStubAMD64_INT_ARG_OFFSET (SIZEOF_MAX_FP_ARG_SPILL + 0x8) +#define UMThunkStubAMD64_METHODDESC_OFFSET SIZEOF_MAX_FP_ARG_SPILL +#define UMThunkStubAMD64_RBP_OFFSET (UMThunkStubAMD64_FIXED_STACK_ALLOC_SIZE + 8) + +// {optional stack args passed to callee} <-- new RSP +// xmm0 <-- RBP +// xmm1 +// xmm2 +// xmm3 +// xmm4 +// xmm5 +// xmm6 +// xmm7 +// METHODDESC_REGISTER +// rdi +// rsi +// rcx +// rdx +// r8 +// r9 +// r12 +// rbp +// return address <-- entry RSP + push_nonvol_reg rbp + mov rbp, rsp + push_nonvol_reg r12 // stack_args + alloc_stack UMThunkStubAMD64_FIXED_STACK_ALLOC_SIZE + save_reg_postrsp rdi, (UMThunkStubAMD64_INT_ARG_OFFSET) + save_reg_postrsp rsi, (UMThunkStubAMD64_INT_ARG_OFFSET + 0x08) + save_reg_postrsp rdx, (UMThunkStubAMD64_INT_ARG_OFFSET + 0x10) + save_reg_postrsp rcx, (UMThunkStubAMD64_INT_ARG_OFFSET + 0x18) + save_reg_postrsp r8, (UMThunkStubAMD64_INT_ARG_OFFSET + 0x20) + save_reg_postrsp r9, (UMThunkStubAMD64_INT_ARG_OFFSET + 0x28) + save_reg_postrsp METHODDESC_REGISTER, UMThunkStubAMD64_METHODDESC_OFFSET + SAVE_FLOAT_ARGUMENT_REGISTERS UMThunkStubAMD64_XMM_SAVE_OFFSET + set_cfa_register rbp, (2*8) + END_PROLOGUE + + // + // Call GetThread() + // + call C_FUNC(GetThread) + test rax, rax + jz LOCAL_LABEL(DoThreadSetup) + +LOCAL_LABEL(HaveThread): + + mov r12, rax // r12 <- Thread* + + //FailFast if a native callable method invoked via ldftn and calli. + cmp dword ptr [r12 + OFFSETOF__Thread__m_fPreemptiveGCDisabled], 1 + jz LOCAL_LABEL(InvalidTransition) + + // + // disable preemptive GC + // + mov dword ptr [r12 + OFFSETOF__Thread__m_fPreemptiveGCDisabled], 1 + + // + // catch returning thread here if a GC is in progress + // + PREPARE_EXTERNAL_VAR g_TrapReturningThreads, rax + cmp dword ptr [rax], 0 + jnz LOCAL_LABEL(DoTrapReturningThreadsTHROW) + +LOCAL_LABEL(InCooperativeMode): + + mov METHODDESC_REGISTER, [rbp - UMThunkStubAMD64_RBP_OFFSET + UMThunkStubAMD64_METHODDESC_OFFSET] + +#if _DEBUG + mov rax, [r12 + OFFSETOF__Thread__m_pDomain] + mov eax, [rax + OFFSETOF__AppDomain__m_dwId] + + mov r11d, [METHODDESC_REGISTER + OFFSETOF__UMEntryThunk__m_dwDomainId] + + cmp rax, r11 + jne LOCAL_LABEL(WrongAppDomain) +#endif + + mov r11, [METHODDESC_REGISTER + OFFSETOF__UMEntryThunk__m_pUMThunkMarshInfo] + mov eax, [r11 + OFFSETOF__UMThunkMarshInfo__m_cbActualArgSize] // stack_args + test rax, rax // stack_args + jnz LOCAL_LABEL(UMThunkStub_CopyStackArgs) // stack_args + +LOCAL_LABEL(UMThunkStub_ArgumentsSetup): + mov rdi, [rbp - UMThunkStubAMD64_RBP_OFFSET + UMThunkStubAMD64_INT_ARG_OFFSET] + mov rsi, [rbp - UMThunkStubAMD64_RBP_OFFSET + UMThunkStubAMD64_INT_ARG_OFFSET + 0x08] + mov rdx, [rbp - UMThunkStubAMD64_RBP_OFFSET + UMThunkStubAMD64_INT_ARG_OFFSET + 0x10] + mov rcx, [rbp - UMThunkStubAMD64_RBP_OFFSET + UMThunkStubAMD64_INT_ARG_OFFSET + 0x18] + mov r8, [rbp - UMThunkStubAMD64_RBP_OFFSET + UMThunkStubAMD64_INT_ARG_OFFSET + 0x20] + mov r9, [rbp - UMThunkStubAMD64_RBP_OFFSET + UMThunkStubAMD64_INT_ARG_OFFSET + 0x28] + movdqa xmm0, xmmword ptr [rbp - UMThunkStubAMD64_RBP_OFFSET + UMThunkStubAMD64_XMM_SAVE_OFFSET] + movdqa xmm1, xmmword ptr [rbp - UMThunkStubAMD64_RBP_OFFSET + UMThunkStubAMD64_XMM_SAVE_OFFSET + 0x10] + movdqa xmm2, xmmword ptr [rbp - UMThunkStubAMD64_RBP_OFFSET + UMThunkStubAMD64_XMM_SAVE_OFFSET + 0x20] + movdqa xmm3, xmmword ptr [rbp - UMThunkStubAMD64_RBP_OFFSET + UMThunkStubAMD64_XMM_SAVE_OFFSET + 0x30] + movdqa xmm4, xmmword ptr [rbp - UMThunkStubAMD64_RBP_OFFSET + UMThunkStubAMD64_XMM_SAVE_OFFSET + 0x40] + movdqa xmm5, xmmword ptr [rbp - UMThunkStubAMD64_RBP_OFFSET + UMThunkStubAMD64_XMM_SAVE_OFFSET + 0x50] + movdqa xmm6, xmmword ptr [rbp - UMThunkStubAMD64_RBP_OFFSET + UMThunkStubAMD64_XMM_SAVE_OFFSET + 0x60] + movdqa xmm7, xmmword ptr [rbp - UMThunkStubAMD64_RBP_OFFSET + UMThunkStubAMD64_XMM_SAVE_OFFSET + 0x70] + + mov rax, [r11 + OFFSETOF__UMThunkMarshInfo__m_pILStub] // rax <- Stub* + call rax + +LOCAL_LABEL(PostCall): + // + // enable preemptive GC + // + mov dword ptr [r12 + OFFSETOF__Thread__m_fPreemptiveGCDisabled], 0 + + // epilog + lea rsp, [rbp - 8] // deallocate arguments + set_cfa_register rsp, (3*8) + pop_nonvol_reg r12 + pop_nonvol_reg rbp + ret + + +LOCAL_LABEL(DoThreadSetup): + call C_FUNC(CreateThreadBlockThrow) + jmp LOCAL_LABEL(HaveThread) + +LOCAL_LABEL(InvalidTransition): + //No arguments to setup , ReversePInvokeBadTransition will failfast + call C_FUNC(ReversePInvokeBadTransition) + +LOCAL_LABEL(DoTrapReturningThreadsTHROW): + mov rdi, r12 // Thread* pThread + mov rsi, [rbp - UMThunkStubAMD64_RBP_OFFSET + UMThunkStubAMD64_METHODDESC_OFFSET] // UMEntryThunk* pUMEntry + call C_FUNC(UMThunkStubRareDisableWorker) + + jmp LOCAL_LABEL(InCooperativeMode) + +LOCAL_LABEL(UMThunkStub_CopyStackArgs): + // rax = cbStackArgs + + sub rsp, rax + and rsp, -16 + + // rax = number of bytes + + lea rdi, [rbp + 0x10] // rbp + ra + lea rsi, [rsp] + +LOCAL_LABEL(CopyLoop): + // rax = number of bytes + // rdi = src + // rsi = dest + // rdx = sratch + + add rax, -8 + mov rdx, [rdi + rax] + mov [rsi + rax], rdx + jnz LOCAL_LABEL(CopyLoop) + + jmp LOCAL_LABEL(UMThunkStub_ArgumentsSetup) + +#if _DEBUG +LOCAL_LABEL(WrongAppDomain): + int3 +#endif + +NESTED_END UMThunkStub, _TEXT + +// +// EXTERN_C void __stdcall UM2MThunk_WrapperHelper( +// void *pThunkArgs, // rdi +// int argLen, // rsi +// void *pAddr, // rdx // not used +// UMEntryThunk *pEntryThunk, // rcx +// Thread *pThread); // r8 +// +NESTED_ENTRY UM2MThunk_WrapperHelper, _TEXT, NoHandler + int3 +NESTED_END UM2MThunk_WrapperHelper, _TEXT diff --git a/src/vm/amd64/unixasmhelpers.S b/src/vm/amd64/unixasmhelpers.S new file mode 100644 index 0000000000..3cf69fb649 --- /dev/null +++ b/src/vm/amd64/unixasmhelpers.S @@ -0,0 +1,231 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +.intel_syntax noprefix +#include "unixasmmacros.inc" +#include "asmconstants.h" + + +////////////////////////////////////////////////////////////////////////// +// +// PrecodeFixupThunk +// +// The call in fixup precode initally points to this function. +// The pupose of this function is to load the MethodDesc and forward the call the prestub. +// +// EXTERN_C VOID __stdcall PrecodeFixupThunk() +LEAF_ENTRY PrecodeFixupThunk, _TEXT + + pop rax // Pop the return address. It points right after the call instruction in the precode. + + // Inline computation done by FixupPrecode::GetMethodDesc() + movzx r10,byte ptr [rax+2] // m_PrecodeChunkIndex + movzx r11,byte ptr [rax+1] // m_MethodDescChunkIndex + mov rax,qword ptr [rax+r10*8+3] + lea METHODDESC_REGISTER,[rax+r11*8] + + // Tail call to prestub + jmp C_FUNC(ThePreStub) + +LEAF_END PrecodeFixupThunk, _TEXT + +// EXTERN_C int __fastcall HelperMethodFrameRestoreState( +// INDEBUG_COMMA(HelperMethodFrame *pFrame) +// MachState *pState +// ) +LEAF_ENTRY HelperMethodFrameRestoreState, _TEXT + +#ifdef _DEBUG + mov rdi, rsi +#endif + + // Check if the MachState is valid + xor eax, eax + cmp qword ptr [rdi + OFFSETOF__MachState___pRetAddr], rax + jne DoRestore + REPRET +DoRestore: + + // + // If a preserved register were pushed onto the stack between + // the managed caller and the H_M_F, m_pReg will point to its + // location on the stack and it would have been updated on the + // stack by the GC already and it will be popped back into the + // appropriate register when the appropriate epilog is run. + // + // Otherwise, the register is preserved across all the code + // in this HCALL or FCALL, so we need to update those registers + // here because the GC will have updated our copies in the + // frame. + // + // So, if m_pReg points into the MachState, we need to update + // the register here. That's what this macro does. + // +#define RestoreReg(reg, regnum) \ + lea rax, [rdi + OFFSETOF__MachState__m_Capture + 8 * regnum]; \ + mov rdx, [rdi + OFFSETOF__MachState__m_Ptrs + 8 * regnum]; \ + cmp rax, rdx; \ + cmove reg, [rax]; + + // regnum has to match ENUM_CALLEE_SAVED_REGISTERS macro + RestoreReg(R12, 0) + RestoreReg(R13, 1) + RestoreReg(R14, 2) + RestoreReg(R15, 3) + RestoreReg(Rbx, 4) + RestoreReg(Rbp, 5) + + xor eax, eax + ret + +LEAF_END HelperMethodFrameRestoreState, _TEXT + +////////////////////////////////////////////////////////////////////////// +// +// NDirectImportThunk +// +// In addition to being called by the EE, this function can be called +// directly from code generated by JIT64 for CRT optimized direct +// P/Invoke calls. If it is modified, the JIT64 compiler's code +// generation will need to altered accordingly. +// +// EXTERN_C VOID __stdcall NDirectImportThunk()// +NESTED_ENTRY NDirectImportThunk, _TEXT, NoHandler + + // + // Save integer parameter registers. + // Make sure to preserve r11 as well as it is used to pass the stack argument size from JIT + // + PUSH_ARGUMENT_REGISTERS + push_register r11 + + // + // Allocate space for XMM parameter registers + // + alloc_stack 0x80 + + SAVE_FLOAT_ARGUMENT_REGISTERS 0 + + END_PROLOGUE + + // + // Call NDirectImportWorker w/ the NDirectMethodDesc* + // + mov rdi, METHODDESC_REGISTER + call C_FUNC(NDirectImportWorker) + + RESTORE_FLOAT_ARGUMENT_REGISTERS 0 + + // + // epilogue, rax contains the native target address + // + free_stack 0x80 + + // + // Restore integer parameter registers and r11 + // + pop_register r11 + POP_ARGUMENT_REGISTERS + + TAILJMP_RAX +NESTED_END NDirectImportThunk, _TEXT + +// EXTERN_C void moveOWord(LPVOID* src, LPVOID* target); +// +// MOVDQA is not an atomic operation. You need to call this function in a crst. +// +LEAF_ENTRY moveOWord, _TEXT + movdqu xmm0, xmmword ptr [rdi] + movdqu xmmword ptr [rsi], xmm0 + + ret +LEAF_END moveOWord, _TEXT + +//------------------------------------------------ +// JIT_RareDisableHelper +// +// The JIT expects this helper to preserve registers used for return values +// +NESTED_ENTRY JIT_RareDisableHelper, _TEXT, NoHandler + + // First integer return register + push_register rax + // Second integer return register + push_register rdx + alloc_stack 0x28 + END_PROLOGUE + // First float return register + movdqa xmmword ptr [rsp], xmm0 + // Second float return register + movdqa xmmword ptr [rsp+0x10], xmm1 + + call C_FUNC(JIT_RareDisableHelperWorker) + + movdqa xmm0, xmmword ptr [rsp] + movdqa xmm1, xmmword ptr [rsp+0x10] + free_stack 0x28 + pop_register rdx + pop_register rax + ret + +NESTED_END JIT_RareDisableHelper, _TEXT + +#ifdef FEATURE_HIJACK + +//------------------------------------------------ +// OnHijackTripThread +// +NESTED_ENTRY OnHijackTripThread, _TEXT, NoHandler + + // Make room for the real return address (rip) + push_register rax + + PUSH_CALLEE_SAVED_REGISTERS + + push_register rdx + // Push rax again - this is where integer/pointer return values are returned + push_register rax + + mov rdi, rsp + + alloc_stack 0x28 + + // First float return register + movdqa [rsp], xmm0 + // Second float return register + movdqa [rsp+0x10], xmm1 + + END_PROLOGUE + + call C_FUNC(OnHijackWorker) + + movdqa xmm0, [rsp] + movdqa xmm1, [rsp+0x10] + free_stack 0x28 + pop_register rax + pop_register rdx + + POP_CALLEE_SAVED_REGISTERS + ret + +NESTED_END OnHijackTripThread, _TEXT + +#endif // FEATURE_HIJACK + +LEAF_ENTRY SinglecastDelegateInvokeStub, _TEXT + + test rdi, rdi + jz NullObject + + + mov rax, [rdi + OFFSETOF__DelegateObject___methodPtr] + mov rdi, [rdi + OFFSETOF__DelegateObject___target] // replace "this" pointer + + jmp rax + +NullObject: + mov rdi, CORINFO_NullReferenceException_ASM + jmp C_FUNC(JIT_InternalThrow) + +LEAF_END SinglecastDelegateInvokeStub, _TEXT diff --git a/src/vm/amd64/unixstubs.cpp b/src/vm/amd64/unixstubs.cpp new file mode 100644 index 0000000000..e7f49577e4 --- /dev/null +++ b/src/vm/amd64/unixstubs.cpp @@ -0,0 +1,90 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "common.h" + +extern "C" +{ + void RedirectForThrowControl() + { + PORTABILITY_ASSERT("Implement for PAL"); + } + + void NakedThrowHelper() + { + PORTABILITY_ASSERT("Implement for PAL"); + } + + void PInvokeStubForHost() + { + PORTABILITY_ASSERT("Implement for PAL"); + } + + void PInvokeStubForHostInner(DWORD dwStackSize, LPVOID pStackFrame, LPVOID pTarget) + { + PORTABILITY_ASSERT("Implement for PAL"); + } + + void ProfileEnterNaked(FunctionIDOrClientID functionIDOrClientID) + { + PORTABILITY_ASSERT("Implement for PAL"); + } + + void ProfileLeaveNaked(FunctionIDOrClientID functionIDOrClientID) + { + PORTABILITY_ASSERT("Implement for PAL"); + } + + void ProfileTailcallNaked(FunctionIDOrClientID functionIDOrClientID) + { + PORTABILITY_ASSERT("Implement for PAL"); + } + + DWORD getcpuid(DWORD arg, unsigned char result[16]) + { + DWORD eax; + __asm(" xor %%ecx, %%ecx\n" \ + " cpuid\n" \ + " mov %%eax, 0(%[result])\n" \ + " mov %%ebx, 4(%[result])\n" \ + " mov %%ecx, 8(%[result])\n" \ + " mov %%edx, 12(%[result])\n" \ + : "=a"(eax) /*output in eax*/\ + : "a"(arg), [result]"r"(result) /*inputs - arg in eax, result in any register*/\ + : "eax", "rbx", "ecx", "edx", "memory" /* registers that are clobbered, *result is clobbered */ + ); + return eax; + } + + DWORD getextcpuid(DWORD arg1, DWORD arg2, unsigned char result[16]) + { + DWORD eax; + __asm(" cpuid\n" \ + " mov %%eax, 0(%[result])\n" \ + " mov %%ebx, 4(%[result])\n" \ + " mov %%ecx, 8(%[result])\n" \ + " mov %%edx, 12(%[result])\n" \ + : "=a"(eax) /*output in eax*/\ + : "c"(arg1), "a"(arg2), [result]"r"(result) /*inputs - arg1 in ecx, arg2 in eax, result in any register*/\ + : "eax", "rbx", "ecx", "edx", "memory" /* registers that are clobbered, *result is clobbered */ + ); + return eax; + } + + DWORD xmmYmmStateSupport() + { + DWORD eax; + __asm(" xgetbv\n" \ + : "=a"(eax) /*output in eax*/\ + : "c"(0) /*inputs - 0 in ecx*/\ + : "eax", "edx" /* registers that are clobbered*/ + ); + // check OS has enabled both XMM and YMM state support + return ((eax & 0x06) == 0x06) ? 1 : 0; + } + + void STDCALL JIT_ProfilerEnterLeaveTailcallStub(UINT_PTR ProfilerHandle) + { + } +}; diff --git a/src/vm/amd64/virtualcallstubamd64.S b/src/vm/amd64/virtualcallstubamd64.S new file mode 100644 index 0000000000..59b5b77dba --- /dev/null +++ b/src/vm/amd64/virtualcallstubamd64.S @@ -0,0 +1,109 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +.intel_syntax noprefix +#include "unixasmmacros.inc" + +// This is the number of times a successful chain lookup will occur before the +// entry is promoted to the front of the chain. This is declared as extern because +// the default value (CALL_STUB_CACHE_INITIAL_SUCCESS_COUNT) is defined in the header. +// extern size_t g_dispatch_cache_chain_success_counter; +#define CHAIN_SUCCESS_COUNTER g_dispatch_cache_chain_success_counter + +// The reason for not using .equ or '=' here is that otherwise the assembler compiles e.g. +// mov rax, BACKPATCH_FLAG as mov rax, [BACKPATCH_FLAG] +#define BACKPATCH_FLAG 1 // Also known as SDF_ResolveBackPatch in the EE +#define PROMOTE_CHAIN_FLAG 2 // Also known as SDF_ResolvePromoteChain in the EE +#define INITIAL_SUCCESS_COUNT 0x100 + +// On Input: +// r11 contains the address of the indirection cell (with the flags in the low bits) +// [rsp+0] m_Datum: contains the dispatch token (slot number or MethodDesc) for the target +// or the ResolveCacheElem when r11 has the PROMOTE_CHAIN_FLAG set +// [rsp+8] m_ReturnAddress: contains the return address of caller to stub + +NESTED_ENTRY ResolveWorkerAsmStub, _TEXT, NoHandler + + PROLOG_WITH_TRANSITION_BLOCK 0, 8, rdx, 0, 0 + + // token stored in rdx by prolog + + lea rdi, [rsp + __PWTB_TransitionBlock] // pTransitionBlock + mov rsi, r11 // indirection cell + flags + mov rcx, rsi + and rcx, 7 // flags + sub rsi, rcx // indirection cell + + call C_FUNC(VSD_ResolveWorker) + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + TAILJMP_RAX + +NESTED_END ResolveWorkerAsmStub, _TEXT + +// extern void ResolveWorkerChainLookupAsmStub() +LEAF_ENTRY ResolveWorkerChainLookupAsmStub, _TEXT +// This will perform a quick chained lookup of the entry if the initial cache lookup fails +// On Input: +// rdx contains our type (MethodTable) +// r10 contains our contract (DispatchToken) +// r11 contains the address of the indirection (and the flags in the low two bits) +// [rsp+0x00] contains the pointer to the ResolveCacheElem +// [rsp+0x08] contains the saved value of rsi +// [rsp+0x10] contains the return address of caller to stub +// + mov rax, BACKPATCH_FLAG // First we check if r11 has the BACKPATCH_FLAG set + and rax, r11 // Set the flags based on (BACKPATCH_FLAG and r11) + pop rax // pop the pointer to the ResolveCacheElem from the top of stack (leaving the flags unchanged) + jnz Fail_RWCLAS // If the BACKPATCH_FLAGS is set we will go directly to the ResolveWorkerAsmStub + +MainLoop_RWCLAS: + mov rax, [rax+18h] // get the next entry in the chain (don't bother checking the first entry again) + test rax,rax // test if we hit a terminating NULL + jz Fail_RWCLAS + + cmp rdx, [rax+00h] // compare our MT with the one in the ResolveCacheElem + jne MainLoop_RWCLAS + cmp r10, [rax+08h] // compare our DispatchToken with one in the ResolveCacheElem + jne MainLoop_RWCLAS +Success_RWCLAS: + PREPARE_EXTERNAL_VAR CHAIN_SUCCESS_COUNTER, rdx + sub qword ptr [rdx],1 // decrement success counter + jl Promote_RWCLAS + mov rax, [rax+10h] // get the ImplTarget + pop rdx + jmp rax + +Promote_RWCLAS: // Move this entry to head postion of the chain + // be quick to reset the counter so we don't get a bunch of contending threads + mov qword ptr [rdx], INITIAL_SUCCESS_COUNT + or r11, PROMOTE_CHAIN_FLAG + mov r10, rax // We pass the ResolveCacheElem to ResolveWorkerAsmStub instead of the DispatchToken +Fail_RWCLAS: + pop rdx // Restore the original saved rdx value + push r10 // pass the DispatchToken or ResolveCacheElem to promote to ResolveWorkerAsmStub + + jmp C_FUNC(ResolveWorkerAsmStub) + +LEAF_END ResolveWorkerChainLookupAsmStub, _TEXT + +#ifdef FEATURE_PREJIT +NESTED_ENTRY StubDispatchFixupStub, _TEXT, NoHandler + + PROLOG_WITH_TRANSITION_BLOCK 0, 0, 0, 0, 0 + + lea rdi, [rsp + __PWTB_TransitionBlock] // pTransitionBlock + mov rsi, r11 // indirection cell address + + mov rdx,0 // sectionIndex + mov rcx,0 // pModule + + call C_FUNC(StubDispatchFixupWorker) + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL +PATCH_LABEL StubDispatchFixupPatchLabel + TAILJMP_RAX + +NESTED_END StubDispatchFixupStub, _TEXT +#endif diff --git a/src/vm/amd64/virtualcallstubcpu.hpp b/src/vm/amd64/virtualcallstubcpu.hpp new file mode 100644 index 0000000000..ee2e2ca719 --- /dev/null +++ b/src/vm/amd64/virtualcallstubcpu.hpp @@ -0,0 +1,790 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +// +// File: AMD64/VirtualCallStubCpu.hpp +// + + + +// + +// See code:VirtualCallStubManager for details +// +// ============================================================================ + +#ifndef _VIRTUAL_CALL_STUB_AMD64_H +#define _VIRTUAL_CALL_STUB_AMD64_H + +#include "dbginterface.h" + +//#define STUB_LOGGING + +#pragma pack(push, 1) +// since we are placing code, we want byte packing of the structs + +#define USES_LOOKUP_STUBS 1 + +/********************************************************************************************* +Stubs that contain code are all part of larger structs called Holders. There is a +Holder for each kind of stub, i.e XXXStub is contained with XXXHolder. Holders are +essentially an implementation trick that allowed rearranging the code sequences more +easily while trying out different alternatives, and for dealing with any alignment +issues in a way that was mostly immune to the actually code sequences. These Holders +should be revisited when the stub code sequences are fixed, since in many cases they +add extra space to a stub that is not really needed. + +Stubs are placed in cache and hash tables. Since unaligned access of data in memory +is very slow, the keys used in those tables should be aligned. The things used as keys +typically also occur in the generated code, e.g. a token as an immediate part of an instruction. +For now, to avoid alignment computations as different code strategies are tried out, the key +fields are all in the Holders. Eventually, many of these fields should be dropped, and the instruction +streams aligned so that the immediate fields fall on aligned boundaries. +*/ + +#if USES_LOOKUP_STUBS + +struct LookupStub; +struct LookupHolder; + +/*LookupStub************************************************************************************** +Virtual and interface call sites are initially setup to point at LookupStubs. +This is because the runtime type of the pointer is not yet known, +so the target cannot be resolved. Note: if the jit is able to determine the runtime type +of the pointer, it should be generating a direct call not a virtual or interface call. +This stub pushes a lookup token onto the stack to identify the sought after method, and then +jumps into the EE (VirtualCallStubManager::ResolveWorkerStub) to effectuate the lookup and +transfer of control to the appropriate target method implementation, perhaps patching of the call site +along the way to point to a more appropriate stub. Hence callsites that point to LookupStubs +get quickly changed to point to another kind of stub. +*/ +struct LookupStub +{ + inline PCODE entryPoint() { LIMITED_METHOD_CONTRACT; return (PCODE)&_entryPoint[0]; } + + inline size_t token() { LIMITED_METHOD_CONTRACT; return _token; } + inline size_t size() { LIMITED_METHOD_CONTRACT; return sizeof(LookupStub); } + +private: + friend struct LookupHolder; + + // The lookup entry point starts with a nop in order to allow us to quickly see + // if the stub is lookup stub or a dispatch stub. We can read thye first byte + // of a stub to find out what kind of a stub we have. + + BYTE _entryPoint [3]; // 90 nop + // 48 B8 mov rax, + size_t _token; // xx xx xx xx xx xx xx xx 64-bit address + BYTE part2 [3]; // 50 push rax + // 48 B8 mov rax, + size_t _resolveWorkerAddr; // xx xx xx xx xx xx xx xx 64-bit address + BYTE part3 [2]; // FF E0 jmp rax +}; + +/* LookupHolders are the containers for LookupStubs, they provide for any alignment of +stubs as necessary. In the case of LookupStubs, alignment is necessary since +LookupStubs are placed in a hash table keyed by token. */ +struct LookupHolder +{ + static void InitializeStatic(); + + void Initialize(PCODE resolveWorkerTarget, size_t dispatchToken); + + LookupStub* stub() { LIMITED_METHOD_CONTRACT; return &_stub; } + + static LookupHolder* FromLookupEntry(PCODE lookupEntry); + +private: + friend struct LookupStub; + + LookupStub _stub; +}; + +#endif // USES_LOOKUP_STUBS + +struct DispatchStub; +struct DispatchStubShort; +struct DispatchStubLong; +struct DispatchHolder; + +/*DispatchStub************************************************************************************** +The structure of a full dispatch stub in memory is a DispatchStub followed contiguously in memory +by either a DispatchStubShort of a DispatchStubLong. DispatchStubShort is used when the resolve +stub (failTarget()) is reachable by a rel32 (DISPL) jump. We make a pretty good effort to make sure +that the stub heaps are set up so that this is the case. If we allocate enough stubs that the heap +end up allocating in a new block that is further away than a DISPL jump can go, then we end up using +a DispatchStubLong which is bigger but is a full 64-bit jump. */ + +/*DispatchStubShort********************************************************************************* +This is the logical continuation of DispatchStub for the case when the failure target is within +a rel32 jump (DISPL). */ +struct DispatchStubShort +{ + friend struct DispatchHolder; + friend struct DispatchStub; + + static BOOL isShortStub(LPCBYTE pCode); + inline PCODE implTarget() const { LIMITED_METHOD_CONTRACT; return (PCODE) _implTarget; } + inline PCODE failTarget() const { LIMITED_METHOD_CONTRACT; return (PCODE) &_failDispl + sizeof(DISPL) + _failDispl; } + +private: + BYTE part1 [2]; // 0f 85 jne + DISPL _failDispl; // xx xx xx xx failEntry ;must be forward jmp for perf reasons + BYTE part2 [2]; // 48 B8 mov rax, + size_t _implTarget; // xx xx xx xx xx xx xx xx 64-bit address + BYTE part3 [2]; // FF E0 jmp rax + + // 31 bytes long, need 1 byte of padding to 8-byte align. + BYTE alignPad [1]; // cc +}; + +inline BOOL DispatchStubShort::isShortStub(LPCBYTE pCode) +{ + LIMITED_METHOD_CONTRACT; + return reinterpret_cast(pCode)->part1[0] == 0x0f; +} + + +/*DispatchStubLong********************************************************************************** +This is the logical continuation of DispatchStub for the case when the failure target is not +reachable by a rel32 jump (DISPL). */ +struct DispatchStubLong +{ + friend struct DispatchHolder; + friend struct DispatchStub; + + static inline BOOL isLongStub(LPCBYTE pCode); + inline PCODE implTarget() const { LIMITED_METHOD_CONTRACT; return (PCODE) _implTarget; } + inline PCODE failTarget() const { LIMITED_METHOD_CONTRACT; return (PCODE) _failTarget; } + +private: + BYTE part1 [1]; // 75 jne + BYTE _failDispl; // xx failLabel + BYTE part2 [2]; // 48 B8 mov rax, + size_t _implTarget; // xx xx xx xx xx xx xx xx 64-bit address + BYTE part3 [2]; // FF E0 jmp rax + // failLabel: + BYTE part4 [2]; // 48 B8 mov rax, + size_t _failTarget; // xx xx xx xx xx xx xx xx 64-bit address + BYTE part5 [2]; // FF E0 jmp rax + + // 39 bytes long, need 1 byte of padding to 8-byte align. + BYTE alignPad [1]; // cc +}; + +inline BOOL DispatchStubLong::isLongStub(LPCBYTE pCode) +{ + LIMITED_METHOD_CONTRACT; + return reinterpret_cast(pCode)->part1[0] == 0x75; +} + +/*DispatchStub************************************************************************************** +Monomorphic and mostly monomorphic call sites eventually point to DispatchStubs. +A dispatch stub has an expected type (expectedMT), target address (target) and fail address (failure). +If the calling frame does in fact have the type be of the expected type, then +control is transfered to the target address, the method implementation. If not, +then control is transfered to the fail address, a fail stub (see below) where a polymorphic +lookup is done to find the correct address to go to. + +implementation note: Order, choice of instructions, and branch directions +should be carefully tuned since it can have an inordinate effect on performance. Particular +attention needs to be paid to the effects on the BTB and branch prediction, both in the small +and in the large, i.e. it needs to run well in the face of BTB overflow--using static predictions. +Note that since this stub is only used for mostly monomorphic callsites (ones that are not, get patched +to something else), therefore the conditional jump "jne failure" is mostly not taken, and hence it is important +that the branch prediction staticly predict this, which means it must be a forward jump. The alternative +is to reverse the order of the jumps and make sure that the resulting conditional jump "je implTarget" +is statically predicted as taken, i.e a backward jump. The current choice was taken since it was easier +to control the placement of the stubs than control the placement of the jitted code and the stubs. */ +struct DispatchStub +{ + friend struct DispatchHolder; + + enum DispatchStubType + { + e_TYPE_SHORT, + e_TYPE_LONG, + }; + + inline DispatchStubType const type() const + { + LIMITED_METHOD_CONTRACT; + CONSISTENCY_CHECK(DispatchStubShort::isShortStub(reinterpret_cast(this + 1)) + || DispatchStubLong::isLongStub(reinterpret_cast(this + 1))); + return DispatchStubShort::isShortStub((BYTE *)(this + 1)) ? e_TYPE_SHORT : e_TYPE_LONG; + } + + inline static size_t size(DispatchStubType type) + { + STATIC_CONTRACT_LEAF; + return sizeof(DispatchStub) + + ((type == e_TYPE_SHORT) ? sizeof(DispatchStubShort) : sizeof(DispatchStubLong)); + } + + inline PCODE entryPoint() const { LIMITED_METHOD_CONTRACT; return (PCODE)&_entryPoint[0]; } + inline size_t expectedMT() const { LIMITED_METHOD_CONTRACT; return _expectedMT; } + inline size_t size() const { WRAPPER_NO_CONTRACT; return size(type()); } + + inline PCODE implTarget() const + { + LIMITED_METHOD_CONTRACT; + if (type() == e_TYPE_SHORT) + return getShortStub()->implTarget(); + else + return getLongStub()->implTarget(); + } + + inline PCODE failTarget() const + { + if (type() == e_TYPE_SHORT) + return getShortStub()->failTarget(); + else + return getLongStub()->failTarget(); + } + +private: + inline DispatchStubShort const *getShortStub() const + { LIMITED_METHOD_CONTRACT; return reinterpret_cast(this + 1); } + + inline DispatchStubLong const *getLongStub() const + { LIMITED_METHOD_CONTRACT; return reinterpret_cast(this + 1); } + + BYTE _entryPoint [2]; // 48 B8 mov rax, + size_t _expectedMT; // xx xx xx xx xx xx xx xx 64-bit address + BYTE part1 [3]; // 48 39 XX cmp [THIS_REG], rax + + // Followed by either DispatchStubShort or DispatchStubLong, depending + // on whether we were able to make a rel32 or had to make an abs64 jump + // to the resolve stub on failure. + +}; + +/* DispatchHolders are the containers for DispatchStubs, they provide for any alignment of +stubs as necessary. DispatchStubs are placed in a hashtable and in a cache. The keys for both +are the pair expectedMT and token. Efficiency of the of the hash table is not a big issue, +since lookups in it are fairly rare. Efficiency of the cache is paramount since it is accessed frequently +(see ResolveStub below). Currently we are storing both of these fields in the DispatchHolder to simplify +alignment issues. If inlineMT in the stub itself was aligned, then it could be the expectedMT field. +While the token field can be logically gotten by following the failure target to the failEntryPoint +of the ResolveStub and then to the token over there, for perf reasons of cache access, it is duplicated here. +This allows us to use DispatchStubs in the cache. The alternative is to provide some other immutable struct +for the cache composed of the triplet (expectedMT, token, target) and some sort of reclaimation scheme when +they are thrown out of the cache via overwrites (since concurrency will make the obvious approaches invalid). +*/ + +/* @workaround for ee resolution - Since the EE does not currently have a resolver function that +does what we want, see notes in implementation of VirtualCallStubManager::Resolver, we are +using dispatch stubs to siumulate what we want. That means that inlineTarget, which should be immutable +is in fact written. Hence we have moved target out into the holder and aligned it so we can +atomically update it. When we get a resolver function that does what we want, we can drop this field, +and live with just the inlineTarget field in the stub itself, since immutability will hold.*/ +struct DispatchHolder +{ + static void InitializeStatic(); + + void Initialize(PCODE implTarget, PCODE failTarget, size_t expectedMT, + DispatchStub::DispatchStubType type); + + static size_t GetHolderSize(DispatchStub::DispatchStubType type) + { STATIC_CONTRACT_WRAPPER; return DispatchStub::size(type); } + + static BOOL CanShortJumpDispatchStubReachFailTarget(PCODE failTarget, LPCBYTE stubMemory) + { + STATIC_CONTRACT_WRAPPER; + LPCBYTE pFrom = stubMemory + sizeof(DispatchStub) + offsetof(DispatchStubShort, part2[0]); + size_t cbRelJump = failTarget - (PCODE)pFrom; + return FitsInI4(cbRelJump); + } + + DispatchStub* stub() { LIMITED_METHOD_CONTRACT; return reinterpret_cast(this); } + + static DispatchHolder* FromDispatchEntry(PCODE dispatchEntry); + +private: + // DispatchStub follows here. It is dynamically sized on allocation + // because it could be a DispatchStubLong or a DispatchStubShort +}; + +struct ResolveStub; +struct ResolveHolder; + +/*ResolveStub************************************************************************************** +Polymorphic call sites and monomorphic calls that fail end up in a ResolverStub. There is only +one resolver stub built for any given token, even though there may be many call sites that +use that token and many distinct types that are used in the calling call frames. A resolver stub +actually has two entry points, one for polymorphic call sites and one for dispatch stubs that fail on their +expectedMT test. There is a third part of the resolver stub that enters the ee when a decision should +be made about changing the callsite. Therefore, we have defined the resolver stub as three distinct pieces, +even though they are actually allocated as a single contiguous block of memory. These pieces are: + +A ResolveStub has two entry points: + +FailEntry - where the dispatch stub goes if the expected MT test fails. This piece of the stub does +a check to see how often we are actually failing. If failures are frequent, control transfers to the +patch piece to cause the call site to be changed from a mostly monomorphic callsite +(calls dispatch stub) to a polymorphic callsize (calls resolve stub). If failures are rare, control +transfers to the resolve piece (see ResolveStub). The failEntryPoint decrements a counter +every time it is entered. The ee at various times will add a large chunk to the counter. + +ResolveEntry - does a lookup via in a cache by hashing the actual type of the calling frame s + and the token identifying the (contract,method) pair desired. If found, control is transfered +to the method implementation. If not found in the cache, the token is pushed and the ee is entered via +the ResolveWorkerStub to do a full lookup and eventual transfer to the correct method implementation. Since +there is a different resolve stub for every token, the token can be inlined and the token can be pre-hashed. +The effectiveness of this approach is highly sensitive to the effectiveness of the hashing algorithm used, +as well as its speed. It turns out it is very important to make the hash function sensitive to all +of the bits of the method table, as method tables are laid out in memory in a very non-random way. Before +making any changes to the code sequences here, it is very important to measure and tune them as perf +can vary greatly, in unexpected ways, with seeming minor changes. + +Implementation note - Order, choice of instructions, and branch directions +should be carefully tuned since it can have an inordinate effect on performance. Particular +attention needs to be paid to the effects on the BTB and branch prediction, both in the small +and in the large, i.e. it needs to run well in the face of BTB overflow--using static predictions. +Note that this stub is called in highly polymorphic cases, but the cache should have been sized +and the hash function chosen to maximize the cache hit case. Hence the cmp/jcc instructions should +mostly be going down the cache hit route, and it is important that this be statically predicted as so. +Hence the 3 jcc instrs need to be forward jumps. As structured, there is only one jmp/jcc that typically +gets put in the BTB since all the others typically fall straight thru. Minimizing potential BTB entries +is important. */ + +struct ResolveStub +{ + inline PCODE failEntryPoint() { LIMITED_METHOD_CONTRACT; return (PCODE)&_failEntryPoint[0]; } + inline PCODE resolveEntryPoint() { LIMITED_METHOD_CONTRACT; return (PCODE)&_resolveEntryPoint[0]; } + inline PCODE slowEntryPoint() { LIMITED_METHOD_CONTRACT; return (PCODE)&_slowEntryPoint[0]; } + + inline INT32* pCounter() { LIMITED_METHOD_CONTRACT; return _pCounter; } + inline UINT32 hashedToken() { LIMITED_METHOD_CONTRACT; return _hashedToken >> LOG2_PTRSIZE; } + inline size_t cacheAddress() { LIMITED_METHOD_CONTRACT; return _cacheAddress; } + inline size_t token() { LIMITED_METHOD_CONTRACT; return _token; } + inline size_t size() { LIMITED_METHOD_CONTRACT; return sizeof(LookupStub); } + +private: + friend struct ResolveHolder; + + BYTE _resolveEntryPoint[3];// resolveStub: + // 52 push rdx + // 49 BA mov r10, + size_t _cacheAddress; // xx xx xx xx xx xx xx xx 64-bit address + BYTE part1 [15]; // 48 8B XX mov rax, [THIS_REG] ; Compute hash = ((MT + MT>>12) ^ prehash) + // 48 8B D0 mov rdx, rax ; rdx <- current MethodTable + // 48 C1 E8 0C shr rax, 12 + // 48 03 C2 add rax, rdx + // 48 35 xor rax, + UINT32 _hashedToken; // xx xx xx xx hashedtoken ; xor with pre-hashed token + BYTE part2 [2]; // 48 25 and rax, + UINT32 mask; // xx xx xx xx cache_mask ; and with cache mask + BYTE part3 [6]; // 4A 8B 04 10 mov rax, [r10 + rax] ; get cache entry address + // 49 BA mov r10, + size_t _token; // xx xx xx xx xx xx xx xx 64-bit address + BYTE part4 [3]; // 48 3B 50 cmp rdx, [rax+ ; compare our MT vs. cache MT + BYTE mtOffset; // xx ResolverCacheElem.pMT] + BYTE part5 [1]; // 75 jne + BYTE toMiss1; // xx miss ; must be forward jump, for perf reasons + BYTE part6 [3]; // 4C 3B 50 cmp r10, [rax+ ; compare our token vs. cache token + BYTE tokenOffset; // xx ResolverCacheElem.token] + BYTE part7 [1]; // 75 jne + BYTE toMiss2; // xx miss ; must be forward jump, for perf reasons + BYTE part8 [3]; // 48 8B 40 mov rax, [rax+ ; setup rax with method impl address + BYTE targetOffset; // xx ResolverCacheElem.target] + BYTE part9 [3]; // 5A pop rdx + // FF E0 jmp rax + // failStub: + BYTE _failEntryPoint [2]; // 48 B8 mov rax, + INT32* _pCounter; // xx xx xx xx xx xx xx xx 64-bit address + BYTE part11 [4]; // 83 00 FF add dword ptr [rax], -1 + // 7d jnl + BYTE toResolveStub1; // xx resolveStub + BYTE part12 [4]; // 49 83 CB 01 or r11, 1 + BYTE _slowEntryPoint [3]; // 52 slow: push rdx + // 49 BA mov r10, + size_t _tokenSlow; // xx xx xx xx xx xx xx xx 64-bit address +// BYTE miss [5]; // 5A miss: pop rdx ; don't pop rdx +// // 41 52 push r10 ; don't push r10 leave it setup with token + BYTE miss [3]; // 50 push rax ; push ptr to cache elem + // 48 B8 mov rax, + size_t _resolveWorker; // xx xx xx xx xx xx xx xx 64-bit address + BYTE part10 [2]; // FF E0 jmp rax +}; + +/* ResolveHolders are the containers for ResolveStubs, They provide +for any alignment of the stubs as necessary. The stubs are placed in a hash table keyed by +the token for which they are built. Efficiency of access requires that this token be aligned. +For now, we have copied that field into the ResolveHolder itself, if the resolve stub is arranged such that +any of its inlined tokens (non-prehashed) is aligned, then the token field in the ResolveHolder +is not needed. */ +struct ResolveHolder +{ + static void InitializeStatic(); + + void Initialize(PCODE resolveWorkerTarget, PCODE patcherTarget, + size_t dispatchToken, UINT32 hashedToken, + void * cacheAddr, INT32* counterAddr); + + ResolveStub* stub() { LIMITED_METHOD_CONTRACT; return &_stub; } + + static ResolveHolder* FromFailEntry(PCODE resolveEntry); + static ResolveHolder* FromResolveEntry(PCODE resolveEntry); + +private: + ResolveStub _stub; +}; +#pragma pack(pop) + +#ifdef DECLARE_DATA + +LookupStub lookupInit; +DispatchStub dispatchInit; +DispatchStubShort dispatchShortInit; +DispatchStubLong dispatchLongInit; +ResolveStub resolveInit; + +#define INSTR_INT3 0xcc +#define INSTR_NOP 0x90 + +#ifndef DACCESS_COMPILE + +#include "asmconstants.h" + +#ifdef STUB_LOGGING +extern size_t g_lookup_inline_counter; +extern size_t g_call_inline_counter; +extern size_t g_miss_inline_counter; +extern size_t g_call_cache_counter; +extern size_t g_miss_cache_counter; +#endif + +/* Template used to generate the stub. We generate a stub by allocating a block of + memory and copy the template over it and just update the specific fields that need + to be changed. +*/ + +void LookupHolder::InitializeStatic() +{ + static_assert_no_msg((sizeof(LookupHolder) % sizeof(void*)) == 0); + + // The first instruction of a LookupStub is nop + // and we use it in order to differentiate the first two bytes + // of a LookupStub and a ResolveStub + lookupInit._entryPoint [0] = INSTR_NOP; + lookupInit._entryPoint [1] = 0x48; + lookupInit._entryPoint [2] = 0xB8; + lookupInit._token = 0xcccccccccccccccc; + lookupInit.part2 [0] = 0x50; + lookupInit.part2 [1] = 0x48; + lookupInit.part2 [2] = 0xB8; + lookupInit._resolveWorkerAddr = 0xcccccccccccccccc; + lookupInit.part3 [0] = 0xFF; + lookupInit.part3 [1] = 0xE0; +} + +void LookupHolder::Initialize(PCODE resolveWorkerTarget, size_t dispatchToken) +{ + _stub = lookupInit; + + //fill in the stub specific fields + _stub._token = dispatchToken; + _stub._resolveWorkerAddr = (size_t) resolveWorkerTarget; +} + +/* Template used to generate the stub. We generate a stub by allocating a block of + memory and copy the template over it and just update the specific fields that need + to be changed. +*/ + +void DispatchHolder::InitializeStatic() +{ + // Check that _expectedMT is aligned in the DispatchHolder + static_assert_no_msg(((sizeof(DispatchStub)+sizeof(DispatchStubShort)) % sizeof(void*)) == 0); + static_assert_no_msg(((sizeof(DispatchStub)+sizeof(DispatchStubLong)) % sizeof(void*)) == 0); + CONSISTENCY_CHECK((offsetof(DispatchStubLong, part4[0]) - offsetof(DispatchStubLong, part2[0])) < INT8_MAX); + + // Common dispatch stub initialization + dispatchInit._entryPoint [0] = 0x48; + dispatchInit._entryPoint [1] = 0xB8; + dispatchInit._expectedMT = 0xcccccccccccccccc; + dispatchInit.part1 [0] = 0x48; + dispatchInit.part1 [1] = 0x39; +#ifdef UNIX_AMD64_ABI + dispatchInit.part1 [2] = 0x07; // RDI +#else + dispatchInit.part1 [2] = 0x01; // RCX +#endif + + // Short dispatch stub initialization + dispatchShortInit.part1 [0] = 0x0F; + dispatchShortInit.part1 [1] = 0x85; + dispatchShortInit._failDispl = 0xcccccccc; + dispatchShortInit.part2 [0] = 0x48; + dispatchShortInit.part2 [1] = 0xb8; + dispatchShortInit._implTarget = 0xcccccccccccccccc; + dispatchShortInit.part3 [0] = 0xFF; + dispatchShortInit.part3 [1] = 0xE0; + dispatchShortInit.alignPad [0] = INSTR_INT3; + + // Long dispatch stub initialization + dispatchLongInit.part1 [0] = 0x75; + dispatchLongInit._failDispl = BYTE(&dispatchLongInit.part4[0] - &dispatchLongInit.part2[0]); + dispatchLongInit.part2 [0] = 0x48; + dispatchLongInit.part2 [1] = 0xb8; + dispatchLongInit._implTarget = 0xcccccccccccccccc; + dispatchLongInit.part3 [0] = 0xFF; + dispatchLongInit.part3 [1] = 0xE0; + // failLabel: + dispatchLongInit.part4 [0] = 0x48; + dispatchLongInit.part4 [1] = 0xb8; + dispatchLongInit._failTarget = 0xcccccccccccccccc; + dispatchLongInit.part5 [0] = 0xFF; + dispatchLongInit.part5 [1] = 0xE0; + dispatchLongInit.alignPad [0] = INSTR_INT3; +}; + +void DispatchHolder::Initialize(PCODE implTarget, PCODE failTarget, size_t expectedMT, + DispatchStub::DispatchStubType type) +{ + // + // Initialize the common area + // + + // initialize the static data + *stub() = dispatchInit; + + // fill in the dynamic data + stub()->_expectedMT = expectedMT; + + // + // Initialize the short/long areas + // + if (type == DispatchStub::e_TYPE_SHORT) + { + DispatchStubShort *shortStub = const_cast(stub()->getShortStub()); + + // initialize the static data + *shortStub = dispatchShortInit; + + // fill in the dynamic data + size_t displ = (failTarget - ((PCODE) &shortStub->_failDispl + sizeof(DISPL))); + CONSISTENCY_CHECK(FitsInI4(displ)); + shortStub->_failDispl = (DISPL) displ; + shortStub->_implTarget = (size_t) implTarget; + CONSISTENCY_CHECK((PCODE)&shortStub->_failDispl + sizeof(DISPL) + shortStub->_failDispl == failTarget); + } + else + { + CONSISTENCY_CHECK(type == DispatchStub::e_TYPE_LONG); + DispatchStubLong *longStub = const_cast(stub()->getLongStub()); + + // initialize the static data + *longStub = dispatchLongInit; + + // fill in the dynamic data + longStub->_implTarget = implTarget; + longStub->_failTarget = failTarget; + } +} + +/* Template used to generate the stub. We generate a stub by allocating a block of + memory and copy the template over it and just update the specific fields that need + to be changed. +*/ + +void ResolveHolder::InitializeStatic() +{ + static_assert_no_msg((sizeof(ResolveHolder) % sizeof(void*)) == 0); + + resolveInit._resolveEntryPoint [0] = 0x52; + resolveInit._resolveEntryPoint [1] = 0x49; + resolveInit._resolveEntryPoint [2] = 0xBA; + resolveInit._cacheAddress = 0xcccccccccccccccc; + resolveInit.part1 [ 0] = 0x48; + resolveInit.part1 [ 1] = 0x8B; +#ifdef UNIX_AMD64_ABI + resolveInit.part1 [ 2] = 0x07; // RDI +#else + resolveInit.part1 [ 2] = 0x01; // RCX +#endif + resolveInit.part1 [ 3] = 0x48; + resolveInit.part1 [ 4] = 0x8B; + resolveInit.part1 [ 5] = 0xD0; + resolveInit.part1 [ 6] = 0x48; + resolveInit.part1 [ 7] = 0xC1; + resolveInit.part1 [ 8] = 0xE8; + resolveInit.part1 [ 9] = CALL_STUB_CACHE_NUM_BITS; + resolveInit.part1 [10] = 0x48; + resolveInit.part1 [11] = 0x03; + resolveInit.part1 [12] = 0xC2; + resolveInit.part1 [13] = 0x48; + resolveInit.part1 [14] = 0x35; +// Review truncation from unsigned __int64 to UINT32 of a constant value. +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4305 4309) +#endif // defined(_MSC_VER) + + resolveInit._hashedToken = 0xcccccccc; + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif // defined(_MSC_VER) + + resolveInit.part2 [ 0] = 0x48; + resolveInit.part2 [ 1] = 0x25; + resolveInit.mask = CALL_STUB_CACHE_MASK*sizeof(void *); + resolveInit.part3 [0] = 0x4A; + resolveInit.part3 [1] = 0x8B; + resolveInit.part3 [2] = 0x04; + resolveInit.part3 [3] = 0x10; + resolveInit.part3 [4] = 0x49; + resolveInit.part3 [5] = 0xBA; + resolveInit._token = 0xcccccccccccccccc; + resolveInit.part4 [0] = 0x48; + resolveInit.part4 [1] = 0x3B; + resolveInit.part4 [2] = 0x50; + resolveInit.mtOffset = offsetof(ResolveCacheElem,pMT) & 0xFF; + resolveInit.part5 [0] = 0x75; + resolveInit.toMiss1 = offsetof(ResolveStub,miss)-(offsetof(ResolveStub,toMiss1)+1) & 0xFF; + resolveInit.part6 [0] = 0x4C; + resolveInit.part6 [1] = 0x3B; + resolveInit.part6 [2] = 0x50; + resolveInit.tokenOffset = offsetof(ResolveCacheElem,token) & 0xFF; + resolveInit.part7 [0] = 0x75; + resolveInit.toMiss2 = offsetof(ResolveStub,miss)-(offsetof(ResolveStub,toMiss2)+1) & 0xFF; + resolveInit.part8 [0] = 0x48; + resolveInit.part8 [1] = 0x8B; + resolveInit.part8 [2] = 0x40; + resolveInit.targetOffset = offsetof(ResolveCacheElem,target) & 0xFF; + resolveInit.part9 [0] = 0x5A; + resolveInit.part9 [1] = 0xFF; + resolveInit.part9 [2] = 0xE0; + resolveInit._failEntryPoint [0] = 0x48; + resolveInit._failEntryPoint [1] = 0xB8; + resolveInit._pCounter = (INT32*) (size_t) 0xcccccccccccccccc; + resolveInit.part11 [0] = 0x83; + resolveInit.part11 [1] = 0x00; + resolveInit.part11 [2] = 0xFF; + resolveInit.part11 [3] = 0x7D; + resolveInit.toResolveStub1 = (offsetof(ResolveStub, _resolveEntryPoint) - (offsetof(ResolveStub, toResolveStub1)+1)) & 0xFF; + resolveInit.part12 [0] = 0x49; + resolveInit.part12 [1] = 0x83; + resolveInit.part12 [2] = 0xCB; + resolveInit.part12 [3] = 0x01; + resolveInit._slowEntryPoint [0] = 0x52; + resolveInit._slowEntryPoint [1] = 0x49; + resolveInit._slowEntryPoint [2] = 0xBA; + resolveInit._tokenSlow = 0xcccccccccccccccc; + resolveInit.miss [0] = 0x50; + resolveInit.miss [1] = 0x48; + resolveInit.miss [2] = 0xB8; + resolveInit._resolveWorker = 0xcccccccccccccccc; + resolveInit.part10 [0] = 0xFF; + resolveInit.part10 [1] = 0xE0; +}; + +void ResolveHolder::Initialize(PCODE resolveWorkerTarget, PCODE patcherTarget, + size_t dispatchToken, UINT32 hashedToken, + void * cacheAddr, INT32* counterAddr) +{ + _stub = resolveInit; + + //fill in the stub specific fields + _stub._cacheAddress = (size_t) cacheAddr; + _stub._hashedToken = hashedToken << LOG2_PTRSIZE; + _stub._token = dispatchToken; + _stub._tokenSlow = dispatchToken; + _stub._resolveWorker = (size_t) resolveWorkerTarget; + _stub._pCounter = counterAddr; +} + +ResolveHolder* ResolveHolder::FromFailEntry(PCODE failEntry) +{ + LIMITED_METHOD_CONTRACT; + ResolveHolder* resolveHolder = (ResolveHolder*) ( failEntry - offsetof(ResolveHolder, _stub) - offsetof(ResolveStub, _failEntryPoint) ); + _ASSERTE(resolveHolder->_stub._resolveEntryPoint[1] == resolveInit._resolveEntryPoint[1]); + return resolveHolder; +} + +#endif // DACCESS_COMPILE + +LookupHolder* LookupHolder::FromLookupEntry(PCODE lookupEntry) +{ + LIMITED_METHOD_CONTRACT; + LookupHolder* lookupHolder = (LookupHolder*) ( lookupEntry - offsetof(LookupHolder, _stub) - offsetof(LookupStub, _entryPoint) ); + _ASSERTE(lookupHolder->_stub._entryPoint[2] == lookupInit._entryPoint[2]); + return lookupHolder; +} + + +DispatchHolder* DispatchHolder::FromDispatchEntry(PCODE dispatchEntry) +{ + LIMITED_METHOD_CONTRACT; + DispatchHolder* dispatchHolder = (DispatchHolder*) ( dispatchEntry - offsetof(DispatchStub, _entryPoint) ); + _ASSERTE(dispatchHolder->stub()->_entryPoint[1] == dispatchInit._entryPoint[1]); + return dispatchHolder; +} + + +ResolveHolder* ResolveHolder::FromResolveEntry(PCODE resolveEntry) +{ + LIMITED_METHOD_CONTRACT; + ResolveHolder* resolveHolder = (ResolveHolder*) ( resolveEntry - offsetof(ResolveHolder, _stub) - offsetof(ResolveStub, _resolveEntryPoint) ); + _ASSERTE(resolveHolder->_stub._resolveEntryPoint[1] == resolveInit._resolveEntryPoint[1]); + return resolveHolder; +} + +VirtualCallStubManager::StubKind VirtualCallStubManager::predictStubKind(PCODE stubStartAddress) +{ +#ifdef DACCESS_COMPILE + return SK_BREAKPOINT; // Dac always uses the slower lookup +#else + StubKind stubKind = SK_UNKNOWN; + + EX_TRY + { + // If stubStartAddress is completely bogus, then this might AV, + // so we protect it with SEH. An AV here is OK. + AVInRuntimeImplOkayHolder AVOkay; + + WORD firstWord = *((WORD*) stubStartAddress); + + if (firstWord == 0xB848) + { + stubKind = SK_DISPATCH; + } + else if (firstWord == 0x4890) + { + stubKind = SK_LOOKUP; + } + else if (firstWord == 0x4952) + { + stubKind = SK_RESOLVE; + } + else if (firstWord == 0x48F8) + { + stubKind = SK_LOOKUP; + } + else + { + BYTE firstByte = ((BYTE*) stubStartAddress)[0]; + BYTE secondByte = ((BYTE*) stubStartAddress)[1]; + + if ((firstByte == INSTR_INT3) || (secondByte == INSTR_INT3)) + { + stubKind = SK_BREAKPOINT; + } + } + } + EX_CATCH + { + stubKind = SK_UNKNOWN; + } + EX_END_CATCH(SwallowAllExceptions); + + return stubKind; + +#endif // DACCESS_COMPILE +} + +#endif //DECLARE_DATA + +#endif // _VIRTUAL_CALL_STUB_AMD64_H diff --git a/src/vm/appdomain.cpp b/src/vm/appdomain.cpp new file mode 100644 index 0000000000..0ec2c5f2fc --- /dev/null +++ b/src/vm/appdomain.cpp @@ -0,0 +1,14915 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + + +#include "common.h" + +#include "appdomain.hpp" +#include "peimagelayout.inl" +#include "field.h" +#include "security.h" +#include "strongnameinternal.h" +#include "excep.h" +#include "eeconfig.h" +#include "gc.h" +#include "eventtrace.h" +#ifdef FEATURE_FUSION +#include "assemblysink.h" +#include "fusion.h" +#include "fusionbind.h" +#include "fusionlogging.h" +#endif +#include "perfcounters.h" +#include "assemblyname.hpp" +#include "eeprofinterfaces.h" +#include "dbginterface.h" +#ifndef DACCESS_COMPILE +#include "eedbginterfaceimpl.h" +#endif +#include "comdynamic.h" +#include "mlinfo.h" +#ifdef FEATURE_REMOTING +#include "remoting.h" +#endif +#include "posterror.h" +#include "assemblynative.hpp" +#include "shimload.h" +#include "stringliteralmap.h" +#include "codeman.h" +#include "comcallablewrapper.h" +#include "apithreadstress.h" +#include "eventtrace.h" +#include "comdelegate.h" +#include "siginfo.hpp" +#ifdef FEATURE_REMOTING +#include "appdomainhelper.h" +#include "objectclone.h" +#endif +#include "typekey.h" + +#include "caparser.h" +#include "ecall.h" +#include "finalizerthread.h" +#include "threadsuspend.h" + +#ifdef FEATURE_PREJIT +#include "corcompile.h" +#include "compile.h" +#endif // FEATURE_PREJIT + +#ifdef FEATURE_COMINTEROP +#include "comtoclrcall.h" +#include "sxshelpers.h" +#include "runtimecallablewrapper.h" +#include "mngstdinterfaces.h" +#include "olevariant.h" +#include "rcwrefcache.h" +#include "olecontexthelpers.h" +#endif // FEATURE_COMINTEROP +#ifdef FEATURE_TYPEEQUIVALENCE +#include "typeequivalencehash.hpp" +#endif + +#include "listlock.inl" +#include "appdomain.inl" +#include "typeparse.h" +#include "mdaassistants.h" +#include "stackcompressor.h" +#ifdef FEATURE_REMOTING +#include "mscorcfg.h" +#include "appdomainconfigfactory.hpp" +#include "crossdomaincalls.h" +#endif +#include "threadpoolrequest.h" + +#include "nativeoverlapped.h" + +#include "compatibilityflags.h" + +#ifndef FEATURE_PAL +#include "dwreport.h" +#endif // !FEATURE_PAL + +#include "stringarraylist.h" + +#ifdef FEATURE_VERSIONING +#include "../binder/inc/clrprivbindercoreclr.h" +#endif + +#if defined(FEATURE_APPX_BINDER) +#include "appxutil.h" +#include "clrprivbinderappx.h" +#endif + +#include "clrprivtypecachewinrt.h" + +#ifndef FEATURE_CORECLR +#include "nlsinfo.h" +#endif + +#ifdef FEATURE_RANDOMIZED_STRING_HASHING +#pragma warning(push) +#pragma warning(disable:4324) +#include "marvin32.h" +#pragma warning(pop) +#endif + +// this file handles string conversion errors for itself +#undef MAKE_TRANSLATIONFAILED + +// Define these macro's to do strict validation for jit lock and class +// init entry leaks. This defines determine if the asserts that +// verify for these leaks are defined or not. These asserts can +// sometimes go off even if no entries have been leaked so this +// defines should be used with caution. +// +// If we are inside a .cctor when the application shut's down then the +// class init lock's head will be set and this will cause the assert +// to go off. +// +// If we are jitting a method when the application shut's down then +// the jit lock's head will be set causing the assert to go off. + +//#define STRICT_CLSINITLOCK_ENTRY_LEAK_DETECTION + +static const WCHAR DEFAULT_DOMAIN_FRIENDLY_NAME[] = W("DefaultDomain"); +static const WCHAR OTHER_DOMAIN_FRIENDLY_NAME_PREFIX[] = W("Domain"); + +#define STATIC_OBJECT_TABLE_BUCKET_SIZE 1020 + +#define MAX_URL_LENGTH 2084 // same as INTERNET_MAX_URL_LENGTH + +//#define _DEBUG_ADUNLOAD 1 + +HRESULT RunDllMain(MethodDesc *pMD, HINSTANCE hInst, DWORD dwReason, LPVOID lpReserved); // clsload.cpp + + + + + +// Statics + +SPTR_IMPL(SystemDomain, SystemDomain, m_pSystemDomain); +SVAL_IMPL(ArrayListStatic, SystemDomain, m_appDomainIndexList); +SPTR_IMPL(SharedDomain, SharedDomain, m_pSharedDomain); +SVAL_IMPL(BOOL, SystemDomain, s_fForceDebug); +SVAL_IMPL(BOOL, SystemDomain, s_fForceProfiling); +SVAL_IMPL(BOOL, SystemDomain, s_fForceInstrument); + +#ifndef DACCESS_COMPILE + +// Base Domain Statics +CrstStatic BaseDomain::m_SpecialStaticsCrst; + +int BaseDomain::m_iNumberOfProcessors = 0; + +// Shared Domain Statics +static BYTE g_pSharedDomainMemory[sizeof(SharedDomain)]; + +// System Domain Statics +GlobalStringLiteralMap* SystemDomain::m_pGlobalStringLiteralMap = NULL; + +static BYTE g_pSystemDomainMemory[sizeof(SystemDomain)]; + +#ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING +size_t SystemDomain::m_totalSurvivedBytes = 0; +#endif //FEATURE_APPDOMAIN_RESOURCE_MONITORING + +CrstStatic SystemDomain::m_SystemDomainCrst; +CrstStatic SystemDomain::m_DelayedUnloadCrst; + +ULONG SystemDomain::s_dNumAppDomains = 0; + +AppDomain * SystemDomain::m_pAppDomainBeingUnloaded = NULL; +ADIndex SystemDomain::m_dwIndexOfAppDomainBeingUnloaded; +Thread *SystemDomain::m_pAppDomainUnloadRequestingThread = 0; +Thread *SystemDomain::m_pAppDomainUnloadingThread = 0; + +ArrayListStatic SystemDomain::m_appDomainIdList; + +DWORD SystemDomain::m_dwLowestFreeIndex = 0; + + + +// comparison function to be used for matching clsids in our clsid hash table +BOOL CompareCLSID(UPTR u1, UPTR u2) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + SO_INTOLERANT; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + GUID *pguid = (GUID *)(u1 << 1); + _ASSERTE(pguid != NULL); + + MethodTable *pMT= (MethodTable *)u2; + _ASSERTE(pMT!= NULL); + + GUID guid; + pMT->GetGuid(&guid, TRUE); + if (!IsEqualIID(guid, *pguid)) + return FALSE; + + return TRUE; +} + +#ifndef CROSSGEN_COMPILE +// Constructor for the LargeHeapHandleBucket class. +LargeHeapHandleBucket::LargeHeapHandleBucket(LargeHeapHandleBucket *pNext, DWORD Size, BaseDomain *pDomain, BOOL bCrossAD) +: m_pNext(pNext) +, m_ArraySize(Size) +, m_CurrentPos(0) +, m_CurrentEmbeddedFreePos(0) // hint for where to start a search for an embedded free item +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(CheckPointer(pDomain)); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + PTRARRAYREF HandleArrayObj; + + // Allocate the array in the large object heap. + if (!bCrossAD) + { + OVERRIDE_TYPE_LOAD_LEVEL_LIMIT(CLASS_LOADED); + HandleArrayObj = (PTRARRAYREF)AllocateObjectArray(Size, g_pObjectClass, TRUE); + } + else + { + // During AD creation we don't want to assign the handle array to the currently running AD but + // to the AD being created. Ensure that AllocateArrayEx doesn't set the AD and then set it here. + AppDomain *pAD = pDomain->AsAppDomain(); + _ASSERTE(pAD); + _ASSERTE(pAD->IsBeingCreated()); + + OBJECTREF array; + { + OVERRIDE_TYPE_LOAD_LEVEL_LIMIT(CLASS_LOADED); + array = AllocateArrayEx( + ClassLoader::LoadArrayTypeThrowing(g_pObjectClass), + (INT32 *)(&Size), + 1, + TRUE + DEBUG_ARG(TRUE)); + } + + array->SetAppDomain(pAD); + + HandleArrayObj = (PTRARRAYREF)array; + } + + // Retrieve the pointer to the data inside the array. This is legal since the array + // is located in the large object heap and is guaranteed not to move. + m_pArrayDataPtr = (OBJECTREF *)HandleArrayObj->GetDataPtr(); + + // Store the array in a strong handle to keep it alive. + m_hndHandleArray = pDomain->CreatePinningHandle((OBJECTREF)HandleArrayObj); +} + + +// Destructor for the LargeHeapHandleBucket class. +LargeHeapHandleBucket::~LargeHeapHandleBucket() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + if (m_hndHandleArray) + { + DestroyPinningHandle(m_hndHandleArray); + m_hndHandleArray = NULL; + } +} + + +// Allocate handles from the bucket. +OBJECTREF *LargeHeapHandleBucket::AllocateHandles(DWORD nRequested) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + _ASSERTE(nRequested > 0 && nRequested <= GetNumRemainingHandles()); + _ASSERTE(m_pArrayDataPtr == (OBJECTREF*)((PTRARRAYREF)ObjectFromHandle(m_hndHandleArray))->GetDataPtr()); + + // Store the handles in the buffer that was passed in + OBJECTREF* ret = &m_pArrayDataPtr[m_CurrentPos]; + m_CurrentPos += nRequested; + + return ret; +} + +// look for a free item embedded in the table +OBJECTREF *LargeHeapHandleBucket::TryAllocateEmbeddedFreeHandle() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + OBJECTREF pPreallocatedSentinalObject = ObjectFromHandle(g_pPreallocatedSentinelObject); + _ASSERTE(pPreallocatedSentinalObject != NULL); + + for (int i = m_CurrentEmbeddedFreePos; i < m_CurrentPos; i++) + { + if (m_pArrayDataPtr[i] == pPreallocatedSentinalObject) + { + m_CurrentEmbeddedFreePos = i; + m_pArrayDataPtr[i] = NULL; + return &m_pArrayDataPtr[i]; + } + } + + // didn't find it (we don't bother wrapping around for a full search, it's not worth it to try that hard, we'll get it next time) + + m_CurrentEmbeddedFreePos = 0; + return NULL; +} + + +// Maximum bucket size will be 64K on 32-bit and 128K on 64-bit. +// We subtract out a small amount to leave room for the object +// header and length of the array. + +#define MAX_BUCKETSIZE (16384 - 4) + +// Constructor for the LargeHeapHandleTable class. +LargeHeapHandleTable::LargeHeapHandleTable(BaseDomain *pDomain, DWORD InitialBucketSize) +: m_pHead(NULL) +, m_pDomain(pDomain) +, m_NextBucketSize(InitialBucketSize) +, m_pFreeSearchHint(NULL) +, m_cEmbeddedFree(0) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(CheckPointer(pDomain)); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + +#ifdef _DEBUG + m_pCrstDebug = NULL; +#endif +} + + +// Destructor for the LargeHeapHandleTable class. +LargeHeapHandleTable::~LargeHeapHandleTable() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + // Delete the buckets. + while (m_pHead) + { + LargeHeapHandleBucket *pOld = m_pHead; + m_pHead = pOld->GetNext(); + delete pOld; + } +} + +//***************************************************************************** +// +// LOCKING RULES FOR AllocateHandles() and ReleaseHandles() 12/08/2004 +// +// +// These functions are not protected by any locking in this location but rather the callers are +// assumed to be doing suitable locking for the handle table. The handle table itself is +// behaving rather like a thread-agnostic collection class -- it doesn't want to know +// much about the outside world and so it is just doing its job with no awareness of +// thread notions. +// +// The instance in question is +// There are two locations you can find a LargeHeapHandleTable +// 1) there is one in every BaseDomain, it is used to keep track of the static members +// in that domain +// 2) there is one in the System Domain that is used for the GlobalStringLiteralMap +// +// the one in (2) is not the same as the one that is in the BaseDomain object that corresponds +// to the SystemDomain -- that one is basically stilborn because the string literals don't go +// there and of course the System Domain has no code loaded into it -- only regular +// AppDomains (like Domain 0) actually execute code. As a result handle tables are in +// practice used either for string literals or for static members but never for both. +// At least not at this writing. +// +// Now it's useful to consider what the locking discipline is for these classes. +// +// --------- +// +// First case: (easiest) is the statics members +// +// Each BaseDomain has its own critical section +// +// BaseDomain::AllocateObjRefPtrsInLargeTable takes a lock with +// CrstHolder ch(&m_LargeHeapHandleTableCrst); +// +// it does this before it calls AllocateHandles which suffices. It does not call ReleaseHandles +// at any time (although ReleaseHandles may be called via AllocateHandles if the request +// doesn't fit in the current block, the remaining handles at the end of the block are released +// automatically as part of allocation/recycling) +// +// note: Recycled handles are only used during String Literal allocation because we only try +// to recycle handles if the allocation request is for exactly one handle. +// +// The handles in the BaseDomain handle table are released when the Domain is unloaded +// as the GC objects become rootless at that time. +// +// This dispenses with all of the Handle tables except the one that is used for string literals +// +// --------- +// +// Second case: Allocation for use in a string literal +// +// AppDomainStringLiteralMap::GetStringLiteral +// leads to calls to +// LargeHeapHandleBlockHolder constructor +// leads to calls to +// m_Data = pOwner->AllocateHandles(nCount); +// +// before doing this AppDomainStringLiteralMap::GetStringLiteral takes this lock +// +// CrstHolder gch(&(SystemDomain::GetGlobalStringLiteralMap()->m_HashTableCrstGlobal)); +// +// which is the lock for the hash table that it owns +// +// STRINGREF *AppDomainStringLiteralMap::GetInternedString +// +// has a similar call path and uses the same approach and the same lock +// this covers all the paths which allocate +// +// --------- +// +// Third case: Releases for use in a string literal entry +// +// CrstHolder gch(&(SystemDomain::GetGlobalStringLiteralMap()->m_HashTableCrstGlobal)); +// taken in the AppDomainStringLiteralMap functions below protects the 4 ways that this can happen +// +// case 3a) +// +// in an appdomain unload case +// +// AppDomainStringLiteralMap::~AppDomainStringLiteralMap() takes the lock then +// leads to calls to +// StringLiteralEntry::Release +// which leads to +// SystemDomain::GetGlobalStringLiteralMapNoCreate()->RemoveStringLiteralEntry(this) +// which leads to +// m_LargeHeapHandleTable.ReleaseHandles((OBJECTREF*)pObjRef, 1); +// +// case 3b) +// +// AppDomainStringLiteralMap::GetStringLiteral() can call StringLiteralEntry::Release in some +// error cases, leading to the same stack as above +// +// case 3c) +// +// AppDomainStringLiteralMap::GetInternedString() can call StringLiteralEntry::Release in some +// error cases, leading to the same stack as above +// +// case 3d) +// +// The same code paths in 3b and 3c and also end up releasing if an exception is thrown +// during their processing. Both these paths use a StringLiteralEntryHolder to assist in cleanup, +// the StaticRelease method of the StringLiteralEntry gets called, which in turn calls the +// Release method. + + +// Allocate handles from the large heap handle table. +OBJECTREF* LargeHeapHandleTable::AllocateHandles(DWORD nRequested, BOOL bCrossAD) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(nRequested > 0); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + // SEE "LOCKING RULES FOR AllocateHandles() and ReleaseHandles()" above + + // the lock must be registered and already held by the caller per contract +#ifdef _DEBUG + _ASSERTE(m_pCrstDebug != NULL); + _ASSERTE(m_pCrstDebug->OwnedByCurrentThread()); +#endif + + if (nRequested == 1 && m_cEmbeddedFree != 0) + { + // special casing singleton requests to look for slots that can be re-used + + // we need to do this because string literals are allocated one at a time and then sometimes + // released. we do not wish for the number of handles consumed by string literals to + // increase forever as assemblies are loaded and unloaded + + if (m_pFreeSearchHint == NULL) + m_pFreeSearchHint = m_pHead; + + while (m_pFreeSearchHint) + { + OBJECTREF* pObjRef = m_pFreeSearchHint->TryAllocateEmbeddedFreeHandle(); + if (pObjRef != NULL) + { + // the slot is to have been prepared with a null ready to go + _ASSERTE(*pObjRef == NULL); + m_cEmbeddedFree--; + return pObjRef; + } + m_pFreeSearchHint = m_pFreeSearchHint->GetNext(); + } + + // the search doesn't wrap around so it's possible that we might have embedded free items + // and not find them but that's ok, we'll get them on the next alloc... all we're trying to do + // is to not have big leaks over time. + } + + + // Retrieve the remaining number of handles in the bucket. + DWORD NumRemainingHandlesInBucket = (m_pHead != NULL) ? m_pHead->GetNumRemainingHandles() : 0; + + // create a new block if this request doesn't fit in the current block + if (nRequested > NumRemainingHandlesInBucket) + { + if (m_pHead != NULL) + { + // mark the handles in that remaining region as available for re-use + ReleaseHandles(m_pHead->CurrentPos(), NumRemainingHandlesInBucket); + + // mark what's left as having been used + m_pHead->ConsumeRemaining(); + } + + // create a new bucket for this allocation + + // We need a block big enough to hold the requested handles + DWORD NewBucketSize = max(m_NextBucketSize, nRequested); + + m_pHead = new LargeHeapHandleBucket(m_pHead, NewBucketSize, m_pDomain, bCrossAD); + + m_NextBucketSize = min(m_NextBucketSize * 2, MAX_BUCKETSIZE); + } + + return m_pHead->AllocateHandles(nRequested); +} + +//***************************************************************************** +// Release object handles allocated using AllocateHandles(). +void LargeHeapHandleTable::ReleaseHandles(OBJECTREF *pObjRef, DWORD nReleased) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + PRECONDITION(CheckPointer(pObjRef)); + } + CONTRACTL_END; + + // SEE "LOCKING RULES FOR AllocateHandles() and ReleaseHandles()" above + + // the lock must be registered and already held by the caller per contract +#ifdef _DEBUG + _ASSERTE(m_pCrstDebug != NULL); + _ASSERTE(m_pCrstDebug->OwnedByCurrentThread()); +#endif + + OBJECTREF pPreallocatedSentinalObject = ObjectFromHandle(g_pPreallocatedSentinelObject); + _ASSERTE(pPreallocatedSentinalObject != NULL); + + + // Add the released handles to the list of available handles. + for (DWORD i = 0; i < nReleased; i++) + { + SetObjectReference(&pObjRef[i], pPreallocatedSentinalObject, NULL); + } + + m_cEmbeddedFree += nReleased; +} + + + + +// Constructor for the ThreadStaticHandleBucket class. +ThreadStaticHandleBucket::ThreadStaticHandleBucket(ThreadStaticHandleBucket *pNext, DWORD Size, BaseDomain *pDomain) +: m_pNext(pNext) +, m_ArraySize(Size) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(CheckPointer(pDomain)); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + PTRARRAYREF HandleArrayObj; + + // Allocate the array on the GC heap. + OVERRIDE_TYPE_LOAD_LEVEL_LIMIT(CLASS_LOADED); + HandleArrayObj = (PTRARRAYREF)AllocateObjectArray(Size, g_pObjectClass, FALSE); + + // Store the array in a strong handle to keep it alive. + m_hndHandleArray = pDomain->CreateStrongHandle((OBJECTREF)HandleArrayObj); +} + +// Destructor for the ThreadStaticHandleBucket class. +ThreadStaticHandleBucket::~ThreadStaticHandleBucket() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + if (m_hndHandleArray) + { + DestroyStrongHandle(m_hndHandleArray); + m_hndHandleArray = NULL; + } +} + +// Allocate handles from the bucket. +OBJECTHANDLE ThreadStaticHandleBucket::GetHandles() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + return m_hndHandleArray; +} + +// Constructor for the ThreadStaticHandleTable class. +ThreadStaticHandleTable::ThreadStaticHandleTable(BaseDomain *pDomain) +: m_pHead(NULL) +, m_pDomain(pDomain) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(CheckPointer(pDomain)); + } + CONTRACTL_END; +} + +// Destructor for the ThreadStaticHandleTable class. +ThreadStaticHandleTable::~ThreadStaticHandleTable() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + // Delete the buckets. + while (m_pHead) + { + ThreadStaticHandleBucket *pOld = m_pHead; + m_pHead = pOld->GetNext(); + delete pOld; + } +} + +// Allocate handles from the large heap handle table. +OBJECTHANDLE ThreadStaticHandleTable::AllocateHandles(DWORD nRequested) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(nRequested > 0); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + // create a new bucket for this allocation + m_pHead = new ThreadStaticHandleBucket(m_pHead, nRequested, m_pDomain); + + return m_pHead->GetHandles(); +} + +#endif // CROSSGEN_COMPILE + + +//***************************************************************************** +// BaseDomain +//***************************************************************************** +void BaseDomain::Attach() +{ +#ifdef FEATURE_RANDOMIZED_STRING_HASHING +#ifdef FEATURE_CORECLR + // Randomized string hashing is on by default for String.GetHashCode in coreclr. + COMNlsHashProvider::s_NlsHashProvider.SetUseRandomHashing((CorHost2::GetStartupFlags() & STARTUP_DISABLE_RANDOMIZED_STRING_HASHING) == 0); +#endif // FEATURE_CORECLR +#endif // FEATURE_RANDOMIZED_STRING_HASHING + m_SpecialStaticsCrst.Init(CrstSpecialStatics); +} + +BaseDomain::BaseDomain() +{ + // initialize fields so the domain can be safely destructed + // shouldn't call anything that can fail here - use ::Init instead + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + FORBID_FAULT; + } + CONTRACTL_END; + + m_fDisableInterfaceCache = FALSE; + + m_pFusionContext = NULL; +#if defined(FEATURE_HOST_ASSEMBLY_RESOLVER) + m_pTPABinderContext = NULL; +#endif + + // Make sure the container is set to NULL so that it gets loaded when it is used. + m_pLargeHeapHandleTable = NULL; + +#ifndef CROSSGEN_COMPILE + // Note that m_hHandleTableBucket is overridden by app domains + m_hHandleTableBucket = g_HandleTableMap.pBuckets[0]; +#else + m_hHandleTableBucket = NULL; +#endif + + m_pMarshalingData = NULL; + + m_dwContextStatics = 0; +#ifdef FEATURE_COMINTEROP + m_pMngStdInterfacesInfo = NULL; + m_pWinRtBinder = NULL; +#endif + m_FileLoadLock.PreInit(); + m_JITLock.PreInit(); + m_ClassInitLock.PreInit(); + m_ILStubGenLock.PreInit(); + +#ifdef FEATURE_REJIT + m_reJitMgr.PreInit(this == (BaseDomain *) g_pSharedDomainMemory); +#endif + +} //BaseDomain::BaseDomain + +//***************************************************************************** +void BaseDomain::Init() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + // + // Initialize the domain locks + // + + if (this == reinterpret_cast(&g_pSharedDomainMemory[0])) + m_DomainCrst.Init(CrstSharedBaseDomain); + else if (this == reinterpret_cast(&g_pSystemDomainMemory[0])) + m_DomainCrst.Init(CrstSystemBaseDomain); + else + m_DomainCrst.Init(CrstBaseDomain); + + m_DomainCacheCrst.Init(CrstAppDomainCache); + m_DomainLocalBlockCrst.Init(CrstDomainLocalBlock); + + m_InteropDataCrst.Init(CrstInteropData, CRST_REENTRANCY); + + m_WinRTFactoryCacheCrst.Init(CrstWinRTFactoryCache, CRST_UNSAFE_COOPGC); + + // NOTE: CRST_UNSAFE_COOPGC prevents a GC mode switch to preemptive when entering this crst. + // If you remove this flag, we will switch to preemptive mode when entering + // m_FileLoadLock, which means all functions that enter it will become + // GC_TRIGGERS. (This includes all uses of PEFileListLockHolder, LoadLockHolder, etc.) So be sure + // to update the contracts if you remove this flag. + m_FileLoadLock.Init(CrstAssemblyLoader, + CrstFlags(CRST_HOST_BREAKABLE), TRUE); + + // + // The JIT lock and the CCtor locks are at the same level (and marked as + // UNSAFE_SAME_LEVEL) because they are all part of the same deadlock detection mechanism. We + // see through cycles of JITting and .cctor execution and then explicitly allow the cycle to + // be broken by giving access to uninitialized classes. If there is no cycle or if the cycle + // involves other locks that arent part of this special deadlock-breaking semantics, then + // we continue to block. + // + m_JITLock.Init(CrstJit, CrstFlags(CRST_REENTRANCY | CRST_UNSAFE_SAMELEVEL), TRUE); + m_ClassInitLock.Init(CrstClassInit, CrstFlags(CRST_REENTRANCY | CRST_UNSAFE_SAMELEVEL), TRUE); + + m_ILStubGenLock.Init(CrstILStubGen, CrstFlags(CRST_REENTRANCY), TRUE); + + // Large heap handle table CRST. + m_LargeHeapHandleTableCrst.Init(CrstAppDomainHandleTable); + + m_crstLoaderAllocatorReferences.Init(CrstLoaderAllocatorReferences); + // Has to switch thread to GC_NOTRIGGER while being held (see code:BaseDomain#AssemblyListLock) + m_crstAssemblyList.Init(CrstAssemblyList, CrstFlags( + CRST_GC_NOTRIGGER_WHEN_TAKEN | CRST_DEBUGGER_THREAD | CRST_TAKEN_DURING_SHUTDOWN)); + + // Initialize the EE marshaling data to NULL. + m_pMarshalingData = NULL; + +#ifdef FEATURE_COMINTEROP + // Allocate the managed standard interfaces information. + m_pMngStdInterfacesInfo = new MngStdInterfacesInfo(); + +#if defined(FEATURE_APPX_BINDER) + if (!AppX::IsAppXProcess()) +#endif + { + CLRPrivBinderWinRT::NamespaceResolutionKind fNamespaceResolutionKind = CLRPrivBinderWinRT::NamespaceResolutionKind_WindowsAPI; + if (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_DesignerNamespaceResolutionEnabled) != FALSE) + { + fNamespaceResolutionKind = CLRPrivBinderWinRT::NamespaceResolutionKind_DesignerResolveEvent; + } + CLRPrivTypeCacheWinRT * pWinRtTypeCache = CLRPrivTypeCacheWinRT::GetOrCreateTypeCache(); + m_pWinRtBinder = CLRPrivBinderWinRT::GetOrCreateBinder(pWinRtTypeCache, fNamespaceResolutionKind); + } +#endif // FEATURE_COMINTEROP + + // Init the COM Interop data hash + { + LockOwner lock = {&m_InteropDataCrst, IsOwnerOfCrst}; + m_interopDataHash.Init(0, NULL, false, &lock); + } + + m_dwSizedRefHandles = 0; + if (!m_iNumberOfProcessors) + { + m_iNumberOfProcessors = GetCurrentProcessCpuCount(); + } +} + +#undef LOADERHEAP_PROFILE_COUNTER + +#ifndef CROSSGEN_COMPILE +//***************************************************************************** +void BaseDomain::Terminate() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + m_crstLoaderAllocatorReferences.Destroy(); + m_DomainCrst.Destroy(); + m_DomainCacheCrst.Destroy(); + m_DomainLocalBlockCrst.Destroy(); + m_InteropDataCrst.Destroy(); + + ListLockEntry* pElement; + + // All the threads that are in this domain had better be stopped by this + // point. + // + // We might be jitting or running a .cctor so we need to empty that queue. + pElement = m_JITLock.Pop(TRUE); + while (pElement) + { +#ifdef STRICT_JITLOCK_ENTRY_LEAK_DETECTION + _ASSERTE ((m_JITLock.m_pHead->m_dwRefCount == 1 + && m_JITLock.m_pHead->m_hrResultCode == E_FAIL) || + dbg_fDrasticShutdown || g_fInControlC); +#endif // STRICT_JITLOCK_ENTRY_LEAK_DETECTION + delete(pElement); + pElement = m_JITLock.Pop(TRUE); + + } + m_JITLock.Destroy(); + + pElement = m_ClassInitLock.Pop(TRUE); + while (pElement) + { +#ifdef STRICT_CLSINITLOCK_ENTRY_LEAK_DETECTION + _ASSERTE (dbg_fDrasticShutdown || g_fInControlC); +#endif + delete(pElement); + pElement = m_ClassInitLock.Pop(TRUE); + } + m_ClassInitLock.Destroy(); + + FileLoadLock* pFileElement; + pFileElement = (FileLoadLock*) m_FileLoadLock.Pop(TRUE); + while (pFileElement) + { +#ifdef STRICT_CLSINITLOCK_ENTRY_LEAK_DETECTION + _ASSERTE (dbg_fDrasticShutdown || g_fInControlC); +#endif + pFileElement->Release(); + pFileElement = (FileLoadLock*) m_FileLoadLock.Pop(TRUE); + } + m_FileLoadLock.Destroy(); + + pElement = m_ILStubGenLock.Pop(TRUE); + while (pElement) + { +#ifdef STRICT_JITLOCK_ENTRY_LEAK_DETECTION + _ASSERTE ((m_ILStubGenLock.m_pHead->m_dwRefCount == 1 + && m_ILStubGenLock.m_pHead->m_hrResultCode == E_FAIL) || + dbg_fDrasticShutdown || g_fInControlC); +#endif // STRICT_JITLOCK_ENTRY_LEAK_DETECTION + delete(pElement); + pElement = m_ILStubGenLock.Pop(TRUE); + } + m_ILStubGenLock.Destroy(); + + m_LargeHeapHandleTableCrst.Destroy(); + + if (m_pLargeHeapHandleTable != NULL) + { + delete m_pLargeHeapHandleTable; + m_pLargeHeapHandleTable = NULL; + } + + if (!IsAppDomain()) + { + // Kind of a workaround - during unloading, we need to have an EE halt + // around deleting this stuff. So it gets deleted in AppDomain::Terminate() + // for those things (because there is a convenient place there.) + GetLoaderAllocator()->CleanupStringLiteralMap(); + } + +#ifdef FEATURE_COMINTEROP + if (m_pMngStdInterfacesInfo) + { + delete m_pMngStdInterfacesInfo; + m_pMngStdInterfacesInfo = NULL; + } + + if (m_pWinRtBinder != NULL) + { + m_pWinRtBinder->Release(); + } +#endif // FEATURE_COMINTEROP + + ClearFusionContext(); + + m_dwSizedRefHandles = 0; +} +#endif // CROSSGEN_COMPILE + +void BaseDomain::InitVSD() +{ + STANDARD_VM_CONTRACT; + + // This is a workaround for gcc, since it fails to successfully resolve + // "TypeIDMap::STARTING_SHARED_DOMAIN_ID" when used within the ?: operator. + UINT32 startingId; + if (IsSharedDomain()) + { + startingId = TypeIDMap::STARTING_SHARED_DOMAIN_ID; + } + else + { + startingId = TypeIDMap::STARTING_UNSHARED_DOMAIN_ID; + } + + // By passing false as the last parameter, interfaces loaded in the + // shared domain will not be given fat type ids if RequiresFatDispatchTokens + // is set. This is correct, as the fat dispatch tokens are only needed to solve + // uniqueness problems involving domain specific types. + m_typeIDMap.Init(startingId, 2, !IsSharedDomain()); + +#ifndef CROSSGEN_COMPILE + GetLoaderAllocator()->InitVirtualCallStubManager(this); +#endif +} + +#ifndef CROSSGEN_COMPILE +BOOL BaseDomain::ContainsOBJECTHANDLE(OBJECTHANDLE handle) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + return Ref_ContainHandle(m_hHandleTableBucket,handle); +} + +DWORD BaseDomain::AllocateContextStaticsOffset(DWORD* pOffsetSlot) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + } + CONTRACTL_END; + + CrstHolder ch(&m_SpecialStaticsCrst); + + DWORD dwOffset = *pOffsetSlot; + + if (dwOffset == (DWORD)-1) + { + // Allocate the slot + dwOffset = m_dwContextStatics++; + *pOffsetSlot = dwOffset; + } + + return dwOffset; +} + +void BaseDomain::ClearFusionContext() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + if(m_pFusionContext) { + m_pFusionContext->Release(); + m_pFusionContext = NULL; + } +#if defined(FEATURE_HOST_ASSEMBLY_RESOLVER) + if (m_pTPABinderContext) { + m_pTPABinderContext->Release(); + m_pTPABinderContext = NULL; + } +#endif +} + +#ifdef FEATURE_PREJIT +void AppDomain::DeleteNativeCodeRanges() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_PREEMPTIVE; + FORBID_FAULT; + } + CONTRACTL_END; + + // Fast path to skip using the assembly iterator when the appdomain has not yet completely been initialized + // and yet we are destroying it. (This is the case if we OOM during AppDomain creation.) + if (m_Assemblies.IsEmpty()) + return; + + // Shutdown assemblies + AssemblyIterator i = IterateAssembliesEx( (AssemblyIterationFlags)(kIncludeLoaded | kIncludeLoading | kIncludeExecution | kIncludeIntrospection | kIncludeFailedToLoad) ); + CollectibleAssemblyHolder pDomainAssembly; + + while (i.Next(pDomainAssembly.This())) + { + Assembly * assembly = pDomainAssembly->m_pAssembly; + if ((assembly != NULL) && !assembly->IsDomainNeutral()) + assembly->DeleteNativeCodeRanges(); + } +} +#endif + +void AppDomain::ShutdownAssemblies() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + // Fast path to skip using the assembly iterator when the appdomain has not yet completely been initialized + // and yet we are destroying it. (This is the case if we OOM during AppDomain creation.) + if (m_Assemblies.IsEmpty()) + return; + + // Shutdown assemblies + // has two stages because Terminate needs info from the Assembly's dependencies + + // Stage 1: call code:Assembly::Terminate + AssemblyIterator i = IterateAssembliesEx((AssemblyIterationFlags)( + kIncludeLoaded | kIncludeLoading | kIncludeExecution | kIncludeIntrospection | kIncludeFailedToLoad | kIncludeCollected)); + DomainAssembly * pDomainAssembly = NULL; + + while (i.Next_UnsafeNoAddRef(&pDomainAssembly)) + { + // Note: cannot use DomainAssembly::GetAssembly() here as it asserts that the assembly has been + // loaded to at least the FILE_LOAD_ALLOCATE level. Since domain shutdown can take place + // asynchronously this property cannot be guaranteed. Access the m_pAssembly field directly instead. + Assembly * assembly = pDomainAssembly->m_pAssembly; + if (assembly && !assembly->IsDomainNeutral()) + assembly->Terminate(); + } + + // Stage 2: Clear the list of assemblies + i = IterateAssembliesEx((AssemblyIterationFlags)( + kIncludeLoaded | kIncludeLoading | kIncludeExecution | kIncludeIntrospection | kIncludeFailedToLoad | kIncludeCollected)); + while (i.Next_UnsafeNoAddRef(&pDomainAssembly)) + { + // We are in shutdown path, no one else can get to the list anymore + delete pDomainAssembly; + } + m_Assemblies.Clear(this); + + // Stage 2: Clear the loader allocators registered for deletion from code:Assembly:Terminate calls in + // stage 1 + // Note: It is not clear to me why we cannot delete the loader allocator from within + // code:DomainAssembly::~DomainAssembly + ShutdownFreeLoaderAllocators(FALSE); +} // AppDomain::ShutdownAssemblies + +void AppDomain::ShutdownFreeLoaderAllocators(BOOL bFromManagedCode) +{ + // If we're called from managed code (i.e. the finalizer thread) we take a lock in + // LoaderAllocator::CleanupFailedTypeInit, which may throw. Otherwise we're called + // from the app-domain shutdown path in which we can avoid taking the lock. + CONTRACTL + { + GC_TRIGGERS; + if (bFromManagedCode) THROWS; else NOTHROW; + MODE_ANY; + CAN_TAKE_LOCK; + } + CONTRACTL_END; + + CrstHolder ch(GetLoaderAllocatorReferencesLock()); + + // Shutdown the LoaderAllocators associated with collectible assemblies + while (m_pDelayedLoaderAllocatorUnloadList != NULL) + { + LoaderAllocator * pCurrentLoaderAllocator = m_pDelayedLoaderAllocatorUnloadList; + // Remove next loader allocator from the list + m_pDelayedLoaderAllocatorUnloadList = m_pDelayedLoaderAllocatorUnloadList->m_pLoaderAllocatorDestroyNext; + + if (bFromManagedCode) + { + // For loader allocator finalization, we need to be careful about cleaning up per-appdomain allocations + // and synchronizing with GC using delay unload list. We need to wait for next Gen2 GC to finish to ensure + // that GC heap does not have any references to the MethodTables being unloaded. + + pCurrentLoaderAllocator->CleanupFailedTypeInit(); + + pCurrentLoaderAllocator->CleanupHandles(); + + GCX_COOP(); + SystemDomain::System()->AddToDelayedUnloadList(pCurrentLoaderAllocator); + } + else + { + // For appdomain unload, delete the loader allocator right away + delete pCurrentLoaderAllocator; + } + } +} // AppDomain::ShutdownFreeLoaderAllocators + +//--------------------------------------------------------------------------------------- +// +// Register the loader allocator for deletion in code:AppDomain::ShutdownFreeLoaderAllocators. +// +void AppDomain::RegisterLoaderAllocatorForDeletion(LoaderAllocator * pLoaderAllocator) +{ + CONTRACTL + { + GC_TRIGGERS; + NOTHROW; + MODE_ANY; + CAN_TAKE_LOCK; + } + CONTRACTL_END; + + CrstHolder ch(GetLoaderAllocatorReferencesLock()); + + pLoaderAllocator->m_pLoaderAllocatorDestroyNext = m_pDelayedLoaderAllocatorUnloadList; + m_pDelayedLoaderAllocatorUnloadList = pLoaderAllocator; +} + +#ifdef FEATURE_CORECLR +void AppDomain::ShutdownNativeDllSearchDirectories() +{ + LIMITED_METHOD_CONTRACT; + // Shutdown assemblies + PathIterator i = IterateNativeDllSearchDirectories(); + + while (i.Next()) + { + delete i.GetPath(); + } + + m_NativeDllSearchDirectories.Clear(); +} +#endif + +void AppDomain::ReleaseDomainBoundInfo() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END;; + // Shutdown assemblies + m_AssemblyCache.OnAppDomainUnload(); + + AssemblyIterator i = IterateAssembliesEx( (AssemblyIterationFlags)(kIncludeFailedToLoad) ); + CollectibleAssemblyHolder pDomainAssembly; + + while (i.Next(pDomainAssembly.This())) + { + pDomainAssembly->ReleaseManagedData(); + } +} + +void AppDomain::ReleaseFiles() +{ + STANDARD_VM_CONTRACT; + + // Shutdown assemblies + AssemblyIterator i = IterateAssembliesEx((AssemblyIterationFlags)( + kIncludeLoaded | kIncludeExecution | kIncludeIntrospection | kIncludeFailedToLoad | kIncludeLoading)); + CollectibleAssemblyHolder pAsm; + + while (i.Next(pAsm.This())) + { + if (pAsm->GetCurrentAssembly() == NULL) + { + // Might be domain neutral or not, but should have no live objects as it has not been + // really loaded yet. Just reset it. + _ASSERTE(FitsIn(i.GetIndex())); + m_Assemblies.Set(this, static_cast(i.GetIndex()), NULL); + delete pAsm.Extract(); + } + else + { + if (!pAsm->GetCurrentAssembly()->IsDomainNeutral()) + pAsm->ReleaseFiles(); + } + } +} // AppDomain::ReleaseFiles + + +OBJECTREF* BaseDomain::AllocateObjRefPtrsInLargeTable(int nRequested, OBJECTREF** ppLazyAllocate, BOOL bCrossAD) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION((nRequested > 0)); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + if (ppLazyAllocate && *ppLazyAllocate) + { + // Allocation already happened + return *ppLazyAllocate; + } + + // Enter preemptive state, take the lock and go back to cooperative mode. + { + CrstHolder ch(&m_LargeHeapHandleTableCrst); + GCX_COOP(); + + if (ppLazyAllocate && *ppLazyAllocate) + { + // Allocation already happened + return *ppLazyAllocate; + } + + // Make sure the large heap handle table is initialized. + if (!m_pLargeHeapHandleTable) + InitLargeHeapHandleTable(); + + // Allocate the handles. + OBJECTREF* result = m_pLargeHeapHandleTable->AllocateHandles(nRequested, bCrossAD); + + if (ppLazyAllocate) + { + *ppLazyAllocate = result; + } + + return result; + } +} +#endif // CROSSGEN_COMPILE + +#endif // !DACCESS_COMPILE + +/*static*/ +PTR_BaseDomain BaseDomain::ComputeBaseDomain( + BaseDomain * pGenericDefinitionDomain, // the domain that owns the generic type or method + Instantiation classInst, // the type arguments to the type (if any) + Instantiation methodInst) // the type arguments to the method (if any) +{ + CONTRACT(PTR_BaseDomain) + { + NOTHROW; + GC_NOTRIGGER; + FORBID_FAULT; + MODE_ANY; + POSTCONDITION(CheckPointer(RETVAL)); + SUPPORTS_DAC; + SO_TOLERANT; + } + CONTRACT_END + + if (pGenericDefinitionDomain && pGenericDefinitionDomain->IsAppDomain()) + RETURN PTR_BaseDomain(pGenericDefinitionDomain); + + for (DWORD i = 0; i < classInst.GetNumArgs(); i++) + { + PTR_BaseDomain pArgDomain = classInst[i].GetDomain(); + if (pArgDomain->IsAppDomain()) + RETURN pArgDomain; + } + + for (DWORD i = 0; i < methodInst.GetNumArgs(); i++) + { + PTR_BaseDomain pArgDomain = methodInst[i].GetDomain(); + if (pArgDomain->IsAppDomain()) + RETURN pArgDomain; + } + RETURN (pGenericDefinitionDomain ? + PTR_BaseDomain(pGenericDefinitionDomain) : + PTR_BaseDomain(SystemDomain::System())); +} + +PTR_BaseDomain BaseDomain::ComputeBaseDomain(TypeKey * pKey) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + SUPPORTS_DAC; + } + CONTRACTL_END; + + + if (pKey->GetKind() == ELEMENT_TYPE_CLASS) + return BaseDomain::ComputeBaseDomain(pKey->GetModule()->GetDomain(), + pKey->GetInstantiation()); + else if (pKey->GetKind() != ELEMENT_TYPE_FNPTR) + return pKey->GetElementType().GetDomain(); + else + return BaseDomain::ComputeBaseDomain(NULL,Instantiation(pKey->GetRetAndArgTypes(), pKey->GetNumArgs()+1)); +} + + + + + +#ifndef DACCESS_COMPILE + +// Insert class in the hash table +void AppDomain::InsertClassForCLSID(MethodTable* pMT, BOOL fForceInsert /*=FALSE*/) +{ + CONTRACTL + { + GC_TRIGGERS; + MODE_ANY; + THROWS; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + CVID cvid; + + // Ensure that registered classes are activated for allocation + pMT->EnsureInstanceActive(); + + // Note that it is possible for multiple classes to claim the same CLSID, and in such a + // case it is arbitrary which one we will return for a future query for a given app domain. + + pMT->GetGuid(&cvid, fForceInsert); + + if (!IsEqualIID(cvid, GUID_NULL)) + { + //@todo get a better key + LPVOID val = (LPVOID)pMT; + { + LockHolder lh(this); + + if (LookupClass(cvid) != pMT) + { + m_clsidHash.InsertValue(GetKeyFromGUID(&cvid), val); + } + } + } +} + +void AppDomain::InsertClassForCLSID(MethodTable* pMT, GUID *pGuid) +{ + CONTRACT_VOID + { + NOTHROW; + PRECONDITION(CheckPointer(pMT)); + PRECONDITION(CheckPointer(pGuid)); + } + CONTRACT_END; + + LPVOID val = (LPVOID)pMT; + { + LockHolder lh(this); + + CVID* cvid = pGuid; + if (LookupClass(*cvid) != pMT) + { + m_clsidHash.InsertValue(GetKeyFromGUID(pGuid), val); + } + } + + RETURN; +} +#endif // DACCESS_COMPILE + +#ifdef FEATURE_COMINTEROP + +#ifndef DACCESS_COMPILE +void AppDomain::CacheTypeByName(const SString &ssClassName, const UINT vCacheVersion, TypeHandle typeHandle, BYTE bFlags, BOOL bReplaceExisting /*= FALSE*/) +{ + WRAPPER_NO_CONTRACT; + LockHolder lh(this); + CacheTypeByNameWorker(ssClassName, vCacheVersion, typeHandle, bFlags, bReplaceExisting); +} + +void AppDomain::CacheTypeByNameWorker(const SString &ssClassName, const UINT vCacheVersion, TypeHandle typeHandle, BYTE bFlags, BOOL bReplaceExisting /*= FALSE*/) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + PRECONDITION(!typeHandle.IsNull()); + } + CONTRACTL_END; + + NewArrayHolder wzClassName(DuplicateStringThrowing(ssClassName.GetUnicode())); + + if (m_vNameToTypeMapVersion != vCacheVersion) + return; + + if (m_pNameToTypeMap == nullptr) + { + m_pNameToTypeMap = new NameToTypeMapTable(); + } + + NameToTypeMapEntry e; + e.m_key.m_wzName = wzClassName; + e.m_key.m_cchName = ssClassName.GetCount(); + e.m_typeHandle = typeHandle; + e.m_nEpoch = this->m_nEpoch; + e.m_bFlags = bFlags; + if (!bReplaceExisting) + m_pNameToTypeMap->Add(e); + else + m_pNameToTypeMap->AddOrReplace(e); + + wzClassName.SuppressRelease(); +} +#endif // DACCESS_COMPILE + +TypeHandle AppDomain::LookupTypeByName(const SString &ssClassName, UINT* pvCacheVersion, BYTE *pbFlags) +{ + WRAPPER_NO_CONTRACT; + LockHolder lh(this); + return LookupTypeByNameWorker(ssClassName, pvCacheVersion, pbFlags); +} + +TypeHandle AppDomain::LookupTypeByNameWorker(const SString &ssClassName, UINT* pvCacheVersion, BYTE *pbFlags) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + SUPPORTS_DAC; + PRECONDITION(CheckPointer(pbFlags, NULL_OK)); + } + CONTRACTL_END; + + *pvCacheVersion = m_vNameToTypeMapVersion; + + if (m_pNameToTypeMap == nullptr) + return TypeHandle(); // a null TypeHandle + + NameToTypeMapEntry::Key key; + key.m_cchName = ssClassName.GetCount(); + key.m_wzName = ssClassName.GetUnicode(); + + const NameToTypeMapEntry * pEntry = m_pNameToTypeMap->LookupPtr(key); + if (pEntry == NULL) + return TypeHandle(); // a null TypeHandle + + if (pbFlags != NULL) + *pbFlags = pEntry->m_bFlags; + + return pEntry->m_typeHandle; +} + +PTR_MethodTable AppDomain::LookupTypeByGuid(const GUID & guid) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + SUPPORTS_DAC; + } + CONTRACTL_END; + + SString sGuid; + { + WCHAR wszGuid[64]; + GuidToLPWSTR(guid, wszGuid, _countof(wszGuid)); + sGuid.Append(wszGuid); + } + UINT ver; + TypeHandle th = LookupTypeByName(sGuid, &ver, NULL); + + if (!th.IsNull()) + { + _ASSERTE(!th.IsTypeDesc()); + return th.AsMethodTable(); + } + +#ifdef FEATURE_PREJIT + else + { + // Next look in each ngen'ed image in turn + AssemblyIterator assemblyIterator = IterateAssembliesEx((AssemblyIterationFlags)( + kIncludeLoaded | kIncludeExecution)); + CollectibleAssemblyHolder pDomainAssembly; + while (assemblyIterator.Next(pDomainAssembly.This())) + { + CollectibleAssemblyHolder pAssembly = pDomainAssembly->GetLoadedAssembly(); + + DomainAssembly::ModuleIterator i = pDomainAssembly->IterateModules(kModIterIncludeLoaded); + while (i.Next()) + { + Module * pModule = i.GetLoadedModule(); + if (!pModule->HasNativeImage()) + continue; + _ASSERTE(!pModule->IsCollectible()); + PTR_MethodTable pMT = pModule->LookupTypeByGuid(guid); + if (pMT != NULL) + { + return pMT; + } + } + } + } +#endif // FEATURE_PREJIT + return NULL; +} + +#ifndef DACCESS_COMPILE +void AppDomain::CacheWinRTTypeByGuid(TypeHandle typeHandle) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(!typeHandle.IsTypeDesc()); + PRECONDITION(CanCacheWinRTTypeByGuid(typeHandle)); + } + CONTRACTL_END; + + PTR_MethodTable pMT = typeHandle.AsMethodTable(); + + GUID guid; + if (pMT->GetGuidForWinRT(&guid)) + { + SString sGuid; + + { + WCHAR wszGuid[64]; + GuidToLPWSTR(guid, wszGuid, _countof(wszGuid)); + sGuid.Append(wszGuid); + } + + BYTE bFlags = 0x80; + TypeHandle th; + UINT vCacheVersion; + { + LockHolder lh(this); + th = LookupTypeByNameWorker(sGuid, &vCacheVersion, &bFlags); + + if (th.IsNull()) + { + // no other entry with the same GUID exists in the cache + CacheTypeByNameWorker(sGuid, vCacheVersion, typeHandle, bFlags); + } + else if (typeHandle.AsMethodTable() != th.AsMethodTable() && th.IsProjectedFromWinRT()) + { + // If we found a native WinRT type cached with the same GUID, replace it. + // Otherwise simply add the new mapping to the cache. + CacheTypeByNameWorker(sGuid, vCacheVersion, typeHandle, bFlags, TRUE); + } + } + } +} +#endif // DACCESS_COMPILE + +void AppDomain::GetCachedWinRTTypes( + SArray * pTypes, + SArray * pGuids, + UINT minEpoch, + UINT * pCurEpoch) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + SUPPORTS_DAC; + } + CONTRACTL_END; + + LockHolder lh(this); + + for (auto it = m_pNameToTypeMap->Begin(), end = m_pNameToTypeMap->End(); + it != end; + ++it) + { + NameToTypeMapEntry entry = (NameToTypeMapEntry)(*it); + TypeHandle th = entry.m_typeHandle; + if (th.AsMethodTable() != NULL && + entry.m_key.m_wzName[0] == W('{') && + entry.m_nEpoch >= minEpoch) + { + _ASSERTE(!th.IsTypeDesc()); + PTR_MethodTable pMT = th.AsMethodTable(); + // we're parsing the GUID value from the cache, because projected types do not cache the + // COM GUID in their GetGuid() but rather the legacy GUID + GUID iid; + if (LPWSTRToGuid(&iid, entry.m_key.m_wzName, 38) && iid != GUID_NULL) + { + pTypes->Append(pMT); + pGuids->Append(iid); + } + } + } + +#ifdef FEATURE_PREJIT + // Next look in each ngen'ed image in turn + AssemblyIterator assemblyIterator = IterateAssembliesEx((AssemblyIterationFlags)( + kIncludeLoaded | kIncludeExecution)); + CollectibleAssemblyHolder pDomainAssembly; + while (assemblyIterator.Next(pDomainAssembly.This())) + { + CollectibleAssemblyHolder pAssembly = pDomainAssembly->GetLoadedAssembly(); + + DomainAssembly::ModuleIterator i = pDomainAssembly->IterateModules(kModIterIncludeLoaded); + while (i.Next()) + { + Module * pModule = i.GetLoadedModule(); + if (!pModule->HasNativeImage()) + continue; + _ASSERTE(!pModule->IsCollectible()); + + pModule->GetCachedWinRTTypes(pTypes, pGuids); + } + } +#endif // FEATURE_PREJIT + + if (pCurEpoch != NULL) + *pCurEpoch = m_nEpoch; + ++m_nEpoch; +} + +#ifndef CROSSGEN_COMPILE +#ifndef DACCESS_COMPILE +// static +void WinRTFactoryCacheTraits::OnDestructPerEntryCleanupAction(const WinRTFactoryCacheEntry& e) +{ + WRAPPER_NO_CONTRACT; + if (e.m_pCtxEntry != NULL) + { + e.m_pCtxEntry->Release(); + } + // the AD is going away, no need to destroy the OBJECTHANDLE +} + +void AppDomain::CacheWinRTFactoryObject(MethodTable *pClassMT, OBJECTREF *refFactory, LPVOID lpCtxCookie) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(CheckPointer(pClassMT)); + } + CONTRACTL_END; + + CtxEntryHolder pNewCtxEntry; + if (lpCtxCookie != NULL) + { + // We don't want to insert the context cookie in the cache because it's just an address + // of an internal COM data structure which will be freed when the apartment is torn down. + // What's worse, if another apartment is later created, its context cookie may have exactly + // the same value leading to incorrect cache hits. We'll use our CtxEntry instead which + // is ref-counted and keeps the COM data structure alive even after the apartment ceases + // to exist. + pNewCtxEntry = CtxEntryCache::GetCtxEntryCache()->FindCtxEntry(lpCtxCookie, GetThread()); + } + + WinRTFactoryCacheLockHolder lh(this); + + if (m_pWinRTFactoryCache == nullptr) + { + m_pWinRTFactoryCache = new WinRTFactoryCache(); + } + + WinRTFactoryCacheEntry *pEntry = const_cast(m_pWinRTFactoryCache->LookupPtr(pClassMT)); + if (!pEntry) + { + // + // No existing entry for this cache + // Create a new one + // + WinRTFactoryCacheEntry e; + + OBJECTHANDLEHolder ohNewHandle(CreateHandle(*refFactory)); + + e.key = pClassMT; + e.m_pCtxEntry = pNewCtxEntry; + e.m_ohFactoryObject = ohNewHandle; + + m_pWinRTFactoryCache->Add(e); + + // suppress release of the CtxEntry and handle after we successfully inserted the new entry + pNewCtxEntry.SuppressRelease(); + ohNewHandle.SuppressRelease(); + } + else + { + // + // Existing entry + // + // release the old CtxEntry and update the entry + CtxEntry *pTemp = pNewCtxEntry.Extract(); + pNewCtxEntry = pEntry->m_pCtxEntry; + pEntry->m_pCtxEntry = pTemp; + + HndAssignHandle(pEntry->m_ohFactoryObject, *refFactory); + } +} + +OBJECTREF AppDomain::LookupWinRTFactoryObject(MethodTable *pClassMT, LPVOID lpCtxCookie) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_COOPERATIVE; + PRECONDITION(CheckPointer(pClassMT)); + PRECONDITION(CheckPointer(m_pWinRTFactoryCache, NULL_OK)); + } + CONTRACTL_END; + + + if (m_pWinRTFactoryCache == nullptr) + return NULL; + + // + // Retrieve cached factory + // + WinRTFactoryCacheLockHolder lh(this); + + const WinRTFactoryCacheEntry *pEntry = m_pWinRTFactoryCache->LookupPtr(pClassMT); + if (pEntry == NULL) + return NULL; + + // + // Ignore factories from a different context, unless lpCtxCookie == NULL, + // which means the factory is free-threaded + // Note that we cannot touch the RCW to retrieve cookie at this point + // because the RCW might belong to a STA thread and that STA thread might die + // and take the RCW with it. Therefore we have to save cookie in this cache + // + if (pEntry->m_pCtxEntry == NULL || pEntry->m_pCtxEntry->GetCtxCookie() == lpCtxCookie) + return ObjectFromHandle(pEntry->m_ohFactoryObject); + + return NULL; +} + +void AppDomain::RemoveWinRTFactoryObjects(LPVOID pCtxCookie) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + if (m_pWinRTFactoryCache == nullptr) + return; + + // helper class for delayed CtxEntry cleanup + class CtxEntryListReleaseHolder + { + public: + CQuickArrayList m_list; + + ~CtxEntryListReleaseHolder() + { + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + for (SIZE_T i = 0; i < m_list.Size(); i++) + { + m_list[i]->Release(); + } + } + } ctxEntryListReleaseHolder; + + GCX_COOP(); + { + WinRTFactoryCacheLockHolder lh(this); + + // Go through the hash table and remove items in the given context + for (WinRTFactoryCache::Iterator it = m_pWinRTFactoryCache->Begin(); it != m_pWinRTFactoryCache->End(); it++) + { + if (it->m_pCtxEntry != NULL && it->m_pCtxEntry->GetCtxCookie() == pCtxCookie) + { + // Releasing the CtxEntry may trigger GC which we can't do under the lock so we push + // it on our local list and release them all after we're done iterating the hashtable. + ctxEntryListReleaseHolder.m_list.Push(it->m_pCtxEntry); + + DestroyHandle(it->m_ohFactoryObject); + m_pWinRTFactoryCache->Remove(it); + } + } + } +} + +OBJECTREF AppDomain::GetMissingObject() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + if (!m_hndMissing) + { + // Get the field + FieldDesc *pValueFD = MscorlibBinder::GetField(FIELD__MISSING__VALUE); + + pValueFD->CheckRunClassInitThrowing(); + + // Retrieve the value static field and store it. + OBJECTHANDLE hndMissing = CreateHandle(pValueFD->GetStaticOBJECTREF()); + + if (FastInterlockCompareExchangePointer(&m_hndMissing, hndMissing, NULL) != NULL) + { + // Exchanged failed. The m_hndMissing did not equal NULL and was returned. + DestroyHandle(hndMissing); + } + } + + return ObjectFromHandle(m_hndMissing); +} + +#endif // DACCESS_COMPILE +#endif //CROSSGEN_COMPILE +#endif // FEATURE_COMINTEROP + +#ifndef DACCESS_COMPILE + +EEMarshalingData *BaseDomain::GetMarshalingData() +{ + CONTRACT (EEMarshalingData*) + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM()); + POSTCONDITION(CheckPointer(m_pMarshalingData)); + } + CONTRACT_END; + + if (!m_pMarshalingData) + { + // Take the lock + CrstHolder holder(&m_InteropDataCrst); + + if (!m_pMarshalingData) + { + LoaderHeap* pHeap = GetLoaderAllocator()->GetLowFrequencyHeap(); + m_pMarshalingData = new (pHeap) EEMarshalingData(this, pHeap, &m_DomainCrst); + } + } + + RETURN m_pMarshalingData; +} + +void BaseDomain::DeleteMarshalingData() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + // We are in shutdown - no need to take any lock + if (m_pMarshalingData) + { + delete m_pMarshalingData; + m_pMarshalingData = NULL; + } +} + +#ifndef CROSSGEN_COMPILE + +STRINGREF *BaseDomain::IsStringInterned(STRINGREF *pString) +{ + CONTRACTL + { + GC_TRIGGERS; + THROWS; + MODE_COOPERATIVE; + PRECONDITION(CheckPointer(pString)); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + return GetLoaderAllocator()->IsStringInterned(pString); +} + +STRINGREF *BaseDomain::GetOrInternString(STRINGREF *pString) +{ + CONTRACTL + { + GC_TRIGGERS; + THROWS; + MODE_COOPERATIVE; + PRECONDITION(CheckPointer(pString)); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + return GetLoaderAllocator()->GetOrInternString(pString); +} + +void BaseDomain::InitLargeHeapHandleTable() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(m_pLargeHeapHandleTable==NULL); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + m_pLargeHeapHandleTable = new LargeHeapHandleTable(this, STATIC_OBJECT_TABLE_BUCKET_SIZE); + +#ifdef _DEBUG + m_pLargeHeapHandleTable->RegisterCrstDebug(&m_LargeHeapHandleTableCrst); +#endif +} + +#ifdef FEATURE_COMINTEROP +MethodTable* AppDomain::GetLicenseInteropHelperMethodTable() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + } + CONTRACTL_END; + + if(m_pLicenseInteropHelperMT == NULL) + { + // Do this work outside of the lock so we don't have an unbreakable lock condition + + TypeHandle licenseMgrTypeHnd; + MethodDescCallSite loadLM(METHOD__MARSHAL__LOAD_LICENSE_MANAGER); + + licenseMgrTypeHnd = (MethodTable*) loadLM.Call_RetLPVOID((ARG_SLOT*)NULL); + + // + // Look up this method by name, because the type is actually declared in System.dll. @todo: why? + // + + MethodDesc *pGetLIHMD = MemberLoader::FindMethod(licenseMgrTypeHnd.AsMethodTable(), + "GetLicenseInteropHelperType", &gsig_SM_Void_RetIntPtr); + _ASSERTE(pGetLIHMD); + + TypeHandle lihTypeHnd; + + MethodDescCallSite getLIH(pGetLIHMD); + lihTypeHnd = (MethodTable*) getLIH.Call_RetLPVOID((ARG_SLOT*)NULL); + + BaseDomain::LockHolder lh(this); + + if(m_pLicenseInteropHelperMT == NULL) + m_pLicenseInteropHelperMT = lihTypeHnd.AsMethodTable(); + } + return m_pLicenseInteropHelperMT; +} + +COMorRemotingFlag AppDomain::GetComOrRemotingFlag() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + // 0. check if the value is already been set + if (m_COMorRemotingFlag != COMorRemoting_NotInitialized) + return m_COMorRemotingFlag; + + // 1. check whether the process is AppX + if (AppX::IsAppXProcess()) + { + // do not use Remoting in AppX + m_COMorRemotingFlag = COMorRemoting_COM; + return m_COMorRemotingFlag; + } + + // 2. check the xml file + m_COMorRemotingFlag = GetPreferComInsteadOfManagedRemotingFromConfigFile(); + if (m_COMorRemotingFlag != COMorRemoting_NotInitialized) + { + return m_COMorRemotingFlag; + } + + // 3. check the global setting + if (NULL != g_pConfig && g_pConfig->ComInsteadOfManagedRemoting()) + { + m_COMorRemotingFlag = COMorRemoting_COM; + } + else + { + m_COMorRemotingFlag = COMorRemoting_Remoting; + } + + return m_COMorRemotingFlag; +} + +BOOL AppDomain::GetPreferComInsteadOfManagedRemoting() +{ + WRAPPER_NO_CONTRACT; + + return (GetComOrRemotingFlag() == COMorRemoting_COM); +} + +STDAPI GetXMLObjectEx(IXMLParser **ppv); + +COMorRemotingFlag AppDomain::GetPreferComInsteadOfManagedRemotingFromConfigFile() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + +#ifdef FEATURE_REMOTING + COMorRemotingFlag res = COMorRemoting_NotInitialized; + NonVMComHolder pIXMLParser(NULL); + NonVMComHolder pFile(NULL); + NonVMComHolder factory(NULL); + + EX_TRY + { + HRESULT hr; + CQuickBytes qb; + + // get config file URL which is a combination of app base and config file name + IfFailGo(m_pFusionContext->PrefetchAppConfigFile()); + + LPWSTR wzConfigFileUrl = (LPWSTR)qb.AllocThrows(MAX_URL_LENGTH * sizeof(WCHAR)); + DWORD dwSize = static_cast(qb.Size()); + + IfFailGo(m_pFusionContext->Get(ACTAG_APP_CFG_LOCAL_FILEPATH, wzConfigFileUrl, &dwSize, 0)); + + IfFailGo(CreateConfigStream(wzConfigFileUrl, &pFile)); + + IfFailGo(GetXMLObjectEx(&pIXMLParser)); + + factory = new (nothrow) AppDomainConfigFactory(); + + if (!factory) { + goto ErrExit; + } + factory->AddRef(); // RefCount = 1 + + + IfFailGo(pIXMLParser->SetInput(pFile)); // filestream's RefCount=2 + + IfFailGo(pIXMLParser->SetFactory(factory)); // factory's RefCount=2 + + IfFailGo(pIXMLParser->Run(-1)); + + res = factory->GetCOMorRemotingFlag(); +ErrExit: ; + + } + EX_CATCH + { + ; + } + EX_END_CATCH(SwallowAllExceptions); + + return res; +#else // FEATURE_REMOTING + return COMorRemoting_COM; +#endif // FEATURE_REMOTING +} +#endif // FEATURE_COMINTEROP + +#endif // CROSSGEN_COMPILE + +//***************************************************************************** +//***************************************************************************** +//***************************************************************************** + +void *SystemDomain::operator new(size_t size, void *pInPlace) +{ + LIMITED_METHOD_CONTRACT; + return pInPlace; +} + + +void SystemDomain::operator delete(void *pMem) +{ + LIMITED_METHOD_CONTRACT; + // Do nothing - new() was in-place +} + + +void SystemDomain::SetCompilationOverrides(BOOL fForceDebug, + BOOL fForceProfiling, + BOOL fForceInstrument) +{ + LIMITED_METHOD_CONTRACT; + s_fForceDebug = fForceDebug; + s_fForceProfiling = fForceProfiling; + s_fForceInstrument = fForceInstrument; +} + +#endif //!DACCESS_COMPILE + +void SystemDomain::GetCompilationOverrides(BOOL * fForceDebug, + BOOL * fForceProfiling, + BOOL * fForceInstrument) +{ + LIMITED_METHOD_DAC_CONTRACT; + *fForceDebug = s_fForceDebug; + *fForceProfiling = s_fForceProfiling; + *fForceInstrument = s_fForceInstrument; +} + +#ifndef DACCESS_COMPILE + +void SystemDomain::Attach() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(m_pSystemDomain == NULL); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + +#ifndef CROSSGEN_COMPILE + // Initialize stub managers + PrecodeStubManager::Init(); + DelegateInvokeStubManager::Init(); + JumpStubStubManager::Init(); + RangeSectionStubManager::Init(); + ILStubManager::Init(); + InteropDispatchStubManager::Init(); + StubLinkStubManager::Init(); + + ThunkHeapStubManager::Init(); + + TailCallStubManager::Init(); + + PerAppDomainTPCountList::InitAppDomainIndexList(); +#endif // CROSSGEN_COMPILE + + m_appDomainIndexList.Init(); + m_appDomainIdList.Init(); + + m_SystemDomainCrst.Init(CrstSystemDomain, (CrstFlags)(CRST_REENTRANCY | CRST_TAKEN_DURING_SHUTDOWN)); + m_DelayedUnloadCrst.Init(CrstSystemDomainDelayedUnloadList, CRST_UNSAFE_COOPGC); + + // Initialize the ID dispenser that is used for domain neutral module IDs + g_pModuleIndexDispenser = new IdDispenser(); + + // Create the global SystemDomain and initialize it. + m_pSystemDomain = new (&g_pSystemDomainMemory[0]) SystemDomain(); + // No way it can fail since g_pSystemDomainMemory is a static array. + CONSISTENCY_CHECK(CheckPointer(m_pSystemDomain)); + + LOG((LF_CLASSLOADER, + LL_INFO10, + "Created system domain at %p\n", + m_pSystemDomain)); + + // We need to initialize the memory pools etc. for the system domain. + m_pSystemDomain->BaseDomain::Init(); // Setup the memory heaps + + // Create the default domain + m_pSystemDomain->CreateDefaultDomain(); + SharedDomain::Attach(); + + // Each domain gets its own ReJitManager, and ReJitManager has its own static + // initialization to run + ReJitManager::InitStatic(); +} + +#ifndef CROSSGEN_COMPILE + +void SystemDomain::DetachBegin() +{ + WRAPPER_NO_CONTRACT; + // Shut down the domain and its children (but don't deallocate anything just + // yet). + + // TODO: we should really not running managed DLLMain during process detach. + if (GetThread() == NULL) + { + return; + } + + if(m_pSystemDomain) + m_pSystemDomain->Stop(); +} + +void SystemDomain::DetachEnd() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + // Shut down the domain and its children (but don't deallocate anything just + // yet). + if(m_pSystemDomain) + { + GCX_PREEMP(); + m_pSystemDomain->ClearFusionContext(); + if (m_pSystemDomain->m_pDefaultDomain) + m_pSystemDomain->m_pDefaultDomain->ClearFusionContext(); + } +} + +void SystemDomain::Stop() +{ + WRAPPER_NO_CONTRACT; + AppDomainIterator i(TRUE); + + while (i.Next()) + if (i.GetDomain()->m_Stage < AppDomain::STAGE_CLEARED) + i.GetDomain()->Stop(); +} + + +void SystemDomain::Terminate() // bNotifyProfiler is ignored +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + // This ignores the refences and terminates the appdomains + AppDomainIterator i(FALSE); + + while (i.Next()) + { + delete i.GetDomain(); + // Keep the iterator from Releasing the current domain + i.m_pCurrent = NULL; + } + + if (m_pSystemFile != NULL) { + m_pSystemFile->Release(); + m_pSystemFile = NULL; + } + + m_pSystemAssembly = NULL; + + if(m_pwDevpath) { + delete[] m_pwDevpath; + m_pwDevpath = NULL; + } + m_dwDevpath = 0; + m_fDevpath = FALSE; + + if (m_pGlobalStringLiteralMap) { + delete m_pGlobalStringLiteralMap; + m_pGlobalStringLiteralMap = NULL; + } + + + SharedDomain::Detach(); + + BaseDomain::Terminate(); + +#ifdef FEATURE_COMINTEROP + if (g_pRCWCleanupList != NULL) + delete g_pRCWCleanupList; +#endif // FEATURE_COMINTEROP + m_GlobalAllocator.Terminate(); +} + + +void SystemDomain::PreallocateSpecialObjects() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + _ASSERTE(g_pPreallocatedSentinelObject == NULL); + + OBJECTREF pPreallocatedSentinalObject = AllocateObject(g_pObjectClass); +#if CHECK_APP_DOMAIN_LEAKS + pPreallocatedSentinalObject->SetSyncBlockAppDomainAgile(); +#endif + g_pPreallocatedSentinelObject = CreatePinningHandle( pPreallocatedSentinalObject ); + +#ifdef FEATURE_PREJIT + if (SystemModule()->HasNativeImage()) + { + CORCOMPILE_EE_INFO_TABLE *pEEInfo = SystemModule()->GetNativeImage()->GetNativeEEInfoTable(); + pEEInfo->emptyString = (CORINFO_Object **)StringObject::GetEmptyStringRefPtr(); + } +#endif +} + +void SystemDomain::CreatePreallocatedExceptions() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + EXCEPTIONREF pBaseException = (EXCEPTIONREF)AllocateObject(g_pExceptionClass); + pBaseException->SetHResult(COR_E_EXCEPTION); + pBaseException->SetXCode(EXCEPTION_COMPLUS); + _ASSERTE(g_pPreallocatedBaseException == NULL); + g_pPreallocatedBaseException = CreateHandle(pBaseException); + + + EXCEPTIONREF pOutOfMemory = (EXCEPTIONREF)AllocateObject(g_pOutOfMemoryExceptionClass); + pOutOfMemory->SetHResult(COR_E_OUTOFMEMORY); + pOutOfMemory->SetXCode(EXCEPTION_COMPLUS); + _ASSERTE(g_pPreallocatedOutOfMemoryException == NULL); + g_pPreallocatedOutOfMemoryException = CreateHandle(pOutOfMemory); + + + EXCEPTIONREF pStackOverflow = (EXCEPTIONREF)AllocateObject(g_pStackOverflowExceptionClass); + pStackOverflow->SetHResult(COR_E_STACKOVERFLOW); + pStackOverflow->SetXCode(EXCEPTION_COMPLUS); + _ASSERTE(g_pPreallocatedStackOverflowException == NULL); + g_pPreallocatedStackOverflowException = CreateHandle(pStackOverflow); + + + EXCEPTIONREF pExecutionEngine = (EXCEPTIONREF)AllocateObject(g_pExecutionEngineExceptionClass); + pExecutionEngine->SetHResult(COR_E_EXECUTIONENGINE); + pExecutionEngine->SetXCode(EXCEPTION_COMPLUS); + _ASSERTE(g_pPreallocatedExecutionEngineException == NULL); + g_pPreallocatedExecutionEngineException = CreateHandle(pExecutionEngine); + + + EXCEPTIONREF pRudeAbortException = (EXCEPTIONREF)AllocateObject(g_pThreadAbortExceptionClass); +#if CHECK_APP_DOMAIN_LEAKS + pRudeAbortException->SetSyncBlockAppDomainAgile(); +#endif + pRudeAbortException->SetHResult(COR_E_THREADABORTED); + pRudeAbortException->SetXCode(EXCEPTION_COMPLUS); + _ASSERTE(g_pPreallocatedRudeThreadAbortException == NULL); + g_pPreallocatedRudeThreadAbortException = CreateHandle(pRudeAbortException); + + + EXCEPTIONREF pAbortException = (EXCEPTIONREF)AllocateObject(g_pThreadAbortExceptionClass); +#if CHECK_APP_DOMAIN_LEAKS + pAbortException->SetSyncBlockAppDomainAgile(); +#endif + pAbortException->SetHResult(COR_E_THREADABORTED); + pAbortException->SetXCode(EXCEPTION_COMPLUS); + _ASSERTE(g_pPreallocatedThreadAbortException == NULL); + g_pPreallocatedThreadAbortException = CreateHandle( pAbortException ); +} +#endif // CROSSGEN_COMPILE + +void SystemDomain::Init() +{ + STANDARD_VM_CONTRACT; + + HRESULT hr = S_OK; + +#ifdef _DEBUG + LOG(( + LF_EEMEM, + LL_INFO10, + "sizeof(EEClass) = %d\n" + "sizeof(MethodTable) = %d\n" + "sizeof(MethodDesc)= %d\n" + "sizeof(FieldDesc) = %d\n" + "sizeof(Module) = %d\n", + sizeof(EEClass), + sizeof(MethodTable), + sizeof(MethodDesc), + sizeof(FieldDesc), + sizeof(Module) + )); +#endif // _DEBUG + + // The base domain is initialized in SystemDomain::Attach() + // to allow stub caches to use the memory pool. Do not + // initialze it here! + +#ifndef CROSSGEN_COMPILE +#ifdef _DEBUG + Context *curCtx = GetCurrentContext(); +#endif + _ASSERTE(curCtx); + _ASSERTE(curCtx->GetDomain() != NULL); +#endif + +#ifdef _DEBUG + g_fVerifierOff = g_pConfig->IsVerifierOff(); +#endif + +#ifdef FEATURE_PREJIT + if (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_ZapDisable) != 0) + g_fAllowNativeImages = false; +#endif + + m_pSystemFile = NULL; + m_pSystemAssembly = NULL; + + DWORD size = 0; + +#ifdef FEATURE_VERSIONING + + // Get the install directory so we can find mscorlib + hr = GetInternalSystemDirectory(NULL, &size); + if (hr != HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) + ThrowHR(hr); + + // GetInternalSystemDirectory returns a size, including the null! + WCHAR *buffer = m_SystemDirectory.OpenUnicodeBuffer(size-1); + IfFailThrow(GetInternalSystemDirectory(buffer, &size)); + m_SystemDirectory.CloseBuffer(); + m_SystemDirectory.Normalize(); + + // At this point m_SystemDirectory should already be canonicalized + +#else + + m_SystemDirectory = GetInternalSystemDirectory(&size); + +#endif // FEATURE_VERSIONING + + m_BaseLibrary.Append(m_SystemDirectory); + if (!m_BaseLibrary.EndsWith(DIRECTORY_SEPARATOR_CHAR_W)) + { + m_BaseLibrary.Append(DIRECTORY_SEPARATOR_CHAR_W); + } + m_BaseLibrary.Append(g_pwBaseLibrary); + m_BaseLibrary.Normalize(); + + LoadBaseSystemClasses(); + + { + // We are about to start allocating objects, so we must be in cooperative mode. + // However, many of the entrypoints to the system (DllGetClassObject and all + // N/Direct exports) get called multiple times. Sometimes they initialize the EE, + // but generally they remain in preemptive mode. So we really want to push/pop + // the state here: + GCX_COOP(); + +#ifndef CROSSGEN_COMPILE + if (!NingenEnabled()) + { + CreatePreallocatedExceptions(); + + PreallocateSpecialObjects(); + } +#endif + + // Finish loading mscorlib now. + m_pSystemAssembly->GetDomainAssembly()->EnsureActive(); +#ifdef FEATURE_FUSION + // disable fusion log for m_pSystemFile, because m_pSystemFile will get reused + m_pSystemFile->DisableFusionLogging(); +#endif + } + +#ifdef _DEBUG + BOOL fPause = EEConfig::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_PauseOnLoad, FALSE); + + while(fPause) + { + ClrSleepEx(20, TRUE); + } +#endif // _DEBUG +} + +#ifndef CROSSGEN_COMPILE +void SystemDomain::LazyInitGlobalStringLiteralMap() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + // Allocate the global string literal map. + NewHolder pGlobalStringLiteralMap(new GlobalStringLiteralMap()); + + // Initialize the global string literal map. + pGlobalStringLiteralMap->Init(); + + if (InterlockedCompareExchangeT(&m_pGlobalStringLiteralMap, pGlobalStringLiteralMap, NULL) == NULL) + { + pGlobalStringLiteralMap.SuppressRelease(); + } +} + +void AppDomain::CreateADUnloadStartEvent() +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + SO_TOLERANT; + MODE_ANY; + } + CONTRACTL_END; + + g_pUnloadStartEvent = new CLREvent(); + g_pUnloadStartEvent->CreateAutoEvent(FALSE); +} + +/*static*/ void SystemDomain::EnumAllStaticGCRefs(promote_func* fn, ScanContext* sc) +{ + CONTRACT_VOID + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACT_END; + + // We don't do a normal AppDomainIterator because we can't take the SystemDomain lock from + // here. + // We're only supposed to call this from a Server GC. We're walking here m_appDomainIdList + // m_appDomainIdList will have an AppDomain* or will be NULL. So the only danger is if we + // Fetch an AppDomain and then in some other thread the AppDomain is deleted. + // + // If the thread deleting the AppDomain (AppDomain::~AppDomain)was in Preemptive mode + // while doing SystemDomain::EnumAllStaticGCRefs we will issue a GCX_COOP(), which will wait + // for the GC to finish, so we are safe + // + // If the thread is in cooperative mode, it must have been suspended for the GC so a delete + // can't happen. + + _ASSERTE(GCHeap::IsGCInProgress() && + GCHeap::IsServerHeap() && + IsGCSpecialThread()); + + SystemDomain* sysDomain = SystemDomain::System(); + if (sysDomain) + { + DWORD i; + DWORD count = (DWORD) m_appDomainIdList.GetCount(); + for (i = 0 ; i < count ; i++) + { + AppDomain* pAppDomain = (AppDomain *)m_appDomainIdList.Get(i); + if (pAppDomain && pAppDomain->IsActive() && !pAppDomain->IsUnloading()) + { +#ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING + if (g_fEnableARM) + { + sc->pCurrentDomain = pAppDomain; + } +#endif //FEATURE_APPDOMAIN_RESOURCE_MONITORING + pAppDomain->EnumStaticGCRefs(fn, sc); + } + } + } + + RETURN; +} + +#ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING +void SystemDomain::ResetADSurvivedBytes() +{ + CONTRACT_VOID + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACT_END; + + _ASSERTE(GCHeap::IsGCInProgress()); + + SystemDomain* sysDomain = SystemDomain::System(); + if (sysDomain) + { + DWORD i; + DWORD count = (DWORD) m_appDomainIdList.GetCount(); + for (i = 0 ; i < count ; i++) + { + AppDomain* pAppDomain = (AppDomain *)m_appDomainIdList.Get(i); + if (pAppDomain && pAppDomain->IsUserActive()) + { + pAppDomain->ResetSurvivedBytes(); + } + } + } + + RETURN; +} + +ULONGLONG SystemDomain::GetADSurvivedBytes() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + SystemDomain* sysDomain = SystemDomain::System(); + ULONGLONG ullTotalADSurvived = 0; + if (sysDomain) + { + DWORD i; + DWORD count = (DWORD) m_appDomainIdList.GetCount(); + for (i = 0 ; i < count ; i++) + { + AppDomain* pAppDomain = (AppDomain *)m_appDomainIdList.Get(i); + if (pAppDomain && pAppDomain->IsUserActive()) + { + ULONGLONG ullSurvived = pAppDomain->GetSurvivedBytes(); + ullTotalADSurvived += ullSurvived; + } + } + } + + return ullTotalADSurvived; +} + +void SystemDomain::RecordTotalSurvivedBytes(size_t totalSurvivedBytes) +{ + CONTRACT_VOID + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACT_END; + + m_totalSurvivedBytes = totalSurvivedBytes; + + SystemDomain* sysDomain = SystemDomain::System(); + if (sysDomain) + { + DWORD i; + DWORD count = (DWORD) m_appDomainIdList.GetCount(); + for (i = 0 ; i < count ; i++) + { + AppDomain* pAppDomain = (AppDomain *)m_appDomainIdList.Get(i); + if (pAppDomain && pAppDomain->IsUserActive()) + { + FireEtwAppDomainMemSurvived((ULONGLONG)pAppDomain, pAppDomain->GetSurvivedBytes(), totalSurvivedBytes, GetClrInstanceId()); + } + } + } + + RETURN; +} +#endif //FEATURE_APPDOMAIN_RESOURCE_MONITORING + +// Only called when EE is suspended. +DWORD SystemDomain::GetTotalNumSizedRefHandles() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + SystemDomain* sysDomain = SystemDomain::System(); + DWORD dwTotalNumSizedRefHandles = 0; + if (sysDomain) + { + DWORD i; + DWORD count = (DWORD) m_appDomainIdList.GetCount(); + for (i = 0 ; i < count ; i++) + { + AppDomain* pAppDomain = (AppDomain *)m_appDomainIdList.Get(i); + if (pAppDomain && pAppDomain->IsActive() && !pAppDomain->IsUnloading()) + { + dwTotalNumSizedRefHandles += pAppDomain->GetNumSizedRefHandles(); + } + } + } + + return dwTotalNumSizedRefHandles; +} +#endif // CROSSGEN_COMPILE + +void SystemDomain::LoadBaseSystemClasses() +{ + STANDARD_VM_CONTRACT; + + ETWOnStartup(LdSysBases_V1, LdSysBasesEnd_V1); + + { +#ifdef FEATURE_FUSION + ETWOnStartup (FusionAppCtx_V1, FusionAppCtxEnd_V1); + // Setup fusion context for the system domain - this is used for binding mscorlib. + IfFailThrow(FusionBind::SetupFusionContext(m_SystemDirectory, NULL, &m_pFusionContext)); + + m_pSystemFile = PEAssembly::OpenSystem(m_pFusionContext); +#else + m_pSystemFile = PEAssembly::OpenSystem(NULL); +#endif // FEATURE_FUSION + } + // Only partially load the system assembly. Other parts of the code will want to access + // the globals in this function before finishing the load. + m_pSystemAssembly = DefaultDomain()->LoadDomainAssembly(NULL, m_pSystemFile, FILE_LOAD_POST_LOADLIBRARY, NULL)->GetCurrentAssembly(); + + // Set up binder for mscorlib + MscorlibBinder::AttachModule(m_pSystemAssembly->GetManifestModule()); + + // Load Object + g_pObjectClass = MscorlibBinder::GetClass(CLASS__OBJECT); + + // get the Object::.ctor method desc so we can special-case it + g_pObjectCtorMD = MscorlibBinder::GetMethod(METHOD__OBJECT__CTOR); + + // Now that ObjectClass is loaded, we can set up + // the system for finalizers. There is no point in deferring this, since we need + // to know this before we allocate our first object. + g_pObjectFinalizerMD = MscorlibBinder::GetMethod(METHOD__OBJECT__FINALIZE); + + + g_pCanonMethodTableClass = MscorlibBinder::GetClass(CLASS____CANON); + + // NOTE: !!!IMPORTANT!!! ValueType and Enum MUST be loaded one immediately after + // the other, because we have coded MethodTable::IsChildValueType + // in such a way that it depends on this behaviour. + // Load the ValueType class + g_pValueTypeClass = MscorlibBinder::GetClass(CLASS__VALUE_TYPE); + + // Load the enum class + g_pEnumClass = MscorlibBinder::GetClass(CLASS__ENUM); + _ASSERTE(!g_pEnumClass->IsValueType()); + + // Load System.RuntimeType + // We need to load this after ValueType and Enum because RuntimeType now + // contains an enum field (m_invocationFlags). Otherwise INVOCATION_FLAGS + // would be treated as a reference type and clr!SigPointer::GetTypeHandleThrowing + // throws an exception. + g_pRuntimeTypeClass = MscorlibBinder::GetClass(CLASS__CLASS); + _ASSERTE(g_pRuntimeTypeClass->IsFullyLoaded()); + + // Load Array class + g_pArrayClass = MscorlibBinder::GetClass(CLASS__ARRAY); + + // Calling a method on IList for an array requires redirection to a method on + // the SZArrayHelper class. Retrieving such methods means calling + // GetActualImplementationForArrayGenericIListMethod, which calls FetchMethod for + // the corresponding method on SZArrayHelper. This basically results in a class + // load due to a method call, which the debugger cannot handle, so we pre-load + // the SZArrayHelper class here. + g_pSZArrayHelperClass = MscorlibBinder::GetClass(CLASS__SZARRAYHELPER); + + // Load Nullable class + g_pNullableClass = MscorlibBinder::GetClass(CLASS__NULLABLE); + + // Load the Object array class. + g_pPredefinedArrayTypes[ELEMENT_TYPE_OBJECT] = ClassLoader::LoadArrayTypeThrowing(TypeHandle(g_pObjectClass)).AsArray(); + + // We have delayed allocation of mscorlib's static handles until we load the object class + MscorlibBinder::GetModule()->AllocateRegularStaticHandles(DefaultDomain()); + + g_TypedReferenceMT = MscorlibBinder::GetClass(CLASS__TYPED_REFERENCE); + + // Make sure all primitive types are loaded + for (int et = ELEMENT_TYPE_VOID; et <= ELEMENT_TYPE_R8; et++) + MscorlibBinder::LoadPrimitiveType((CorElementType)et); + + MscorlibBinder::LoadPrimitiveType(ELEMENT_TYPE_I); + MscorlibBinder::LoadPrimitiveType(ELEMENT_TYPE_U); + + // unfortunately, the following cannot be delay loaded since the jit + // uses it to compute method attributes within a function that cannot + // handle Complus exception and the following call goes through a path + // where a complus exception can be thrown. It is unfortunate, because + // we know that the delegate class and multidelegate class are always + // guaranteed to be found. + g_pDelegateClass = MscorlibBinder::GetClass(CLASS__DELEGATE); + g_pMulticastDelegateClass = MscorlibBinder::GetClass(CLASS__MULTICAST_DELEGATE); + + // used by IsImplicitInterfaceOfSZArray + MscorlibBinder::GetClass(CLASS__IENUMERABLEGENERIC); + MscorlibBinder::GetClass(CLASS__ICOLLECTIONGENERIC); + MscorlibBinder::GetClass(CLASS__ILISTGENERIC); + MscorlibBinder::GetClass(CLASS__IREADONLYCOLLECTIONGENERIC); + MscorlibBinder::GetClass(CLASS__IREADONLYLISTGENERIC); + + // Load String + g_pStringClass = MscorlibBinder::LoadPrimitiveType(ELEMENT_TYPE_STRING); + _ASSERTE(g_pStringClass->GetBaseSize() == ObjSizeOf(StringObject)+sizeof(WCHAR)); + _ASSERTE(g_pStringClass->GetComponentSize() == 2); + + // Used by Buffer::BlockCopy + g_pByteArrayMT = ClassLoader::LoadArrayTypeThrowing( + TypeHandle(MscorlibBinder::GetElementType(ELEMENT_TYPE_U1))).AsArray()->GetMethodTable(); + +#ifndef CROSSGEN_COMPILE + ECall::PopulateManagedStringConstructors(); + + if (CLRIoCompletionHosted()) + { + g_pOverlappedDataClass = MscorlibBinder::GetClass(CLASS__OVERLAPPEDDATA); + _ASSERTE (g_pOverlappedDataClass); + if (CorHost2::GetHostOverlappedExtensionSize() != 0) + { + // Overlapped may have an extension if a host hosts IO completion subsystem + DWORD instanceFieldBytes = g_pOverlappedDataClass->GetNumInstanceFieldBytes() + CorHost2::GetHostOverlappedExtensionSize(); + _ASSERTE (instanceFieldBytes + ObjSizeOf(Object) >= MIN_OBJECT_SIZE); + DWORD baseSize = (DWORD) (instanceFieldBytes + ObjSizeOf(Object)); + baseSize = (baseSize + ALLOC_ALIGN_CONSTANT) & ~ALLOC_ALIGN_CONSTANT; // m_BaseSize must be aligned + DWORD adjustSize = baseSize - g_pOverlappedDataClass->GetBaseSize(); + CGCDesc* map = CGCDesc::GetCGCDescFromMT(g_pOverlappedDataClass); + CGCDescSeries * cur = map->GetHighestSeries(); + _ASSERTE ((SSIZE_T)map->GetNumSeries() == 1); + cur->SetSeriesSize(cur->GetSeriesSize() - adjustSize); + g_pOverlappedDataClass->SetBaseSize(baseSize); + } + } +#endif // CROSSGEN_COMPILE + + g_pExceptionClass = MscorlibBinder::GetClass(CLASS__EXCEPTION); + g_pOutOfMemoryExceptionClass = MscorlibBinder::GetException(kOutOfMemoryException); + g_pStackOverflowExceptionClass = MscorlibBinder::GetException(kStackOverflowException); + g_pExecutionEngineExceptionClass = MscorlibBinder::GetException(kExecutionEngineException); + g_pThreadAbortExceptionClass = MscorlibBinder::GetException(kThreadAbortException); + + // Used for determining whether a class has a critical finalizer + // To determine whether a class has a critical finalizer, we + // currently will simply see if it's parent class has a critical + // finalizer. To introduce a class with a critical finalizer, + // we'll explicitly load CriticalFinalizerObject and set the bit + // here. + g_pCriticalFinalizerObjectClass = MscorlibBinder::GetClass(CLASS__CRITICAL_FINALIZER_OBJECT); + _ASSERTE(g_pCriticalFinalizerObjectClass->HasCriticalFinalizer()); + + // used by gc to handle predefined agility checking + g_pThreadClass = MscorlibBinder::GetClass(CLASS__THREAD); + +#ifdef FEATURE_COMINTEROP + g_pBaseCOMObject = MscorlibBinder::GetClass(CLASS__COM_OBJECT); + g_pBaseRuntimeClass = MscorlibBinder::GetClass(CLASS__RUNTIME_CLASS); + + MscorlibBinder::GetClass(CLASS__IDICTIONARYGENERIC); + MscorlibBinder::GetClass(CLASS__IREADONLYDICTIONARYGENERIC); + MscorlibBinder::GetClass(CLASS__ATTRIBUTE); + MscorlibBinder::GetClass(CLASS__EVENT_HANDLERGENERIC); + + MscorlibBinder::GetClass(CLASS__IENUMERABLE); + MscorlibBinder::GetClass(CLASS__ICOLLECTION); + MscorlibBinder::GetClass(CLASS__ILIST); + MscorlibBinder::GetClass(CLASS__IDISPOSABLE); + +#ifdef _DEBUG + WinRTInterfaceRedirector::VerifyRedirectedInterfaceStubs(); +#endif // _DEBUG +#endif + +#ifdef FEATURE_ICASTABLE + g_pICastableInterface = MscorlibBinder::GetClass(CLASS__ICASTABLE); +#endif // FEATURE_ICASTABLE + + // Load a special marker method used to detect Constrained Execution Regions + // at jit time. + g_pPrepareConstrainedRegionsMethod = MscorlibBinder::GetMethod(METHOD__RUNTIME_HELPERS__PREPARE_CONSTRAINED_REGIONS); + g_pExecuteBackoutCodeHelperMethod = MscorlibBinder::GetMethod(METHOD__RUNTIME_HELPERS__EXECUTE_BACKOUT_CODE_HELPER); + + // Make sure that FCall mapping for Monitor.Enter is initialized. We need it in case Monitor.Enter is used only as JIT helper. + // For more details, see comment in code:JITutil_MonEnterWorker around "__me = GetEEFuncEntryPointMacro(JIT_MonEnter)". + ECall::GetFCallImpl(MscorlibBinder::GetMethod(METHOD__MONITOR__ENTER)); + +#ifdef PROFILING_SUPPORTED + // Note that g_profControlBlock.fBaseSystemClassesLoaded must be set to TRUE only after + // all base system classes are loaded. Profilers are not allowed to call any type-loading + // APIs until g_profControlBlock.fBaseSystemClassesLoaded is TRUE. It is important that + // all base system classes need to be loaded before profilers can trigger the type loading. + g_profControlBlock.fBaseSystemClassesLoaded = TRUE; +#endif // PROFILING_SUPPORTED + +#if defined(_DEBUG) && !defined(CROSSGEN_COMPILE) + if (!NingenEnabled()) + { + g_Mscorlib.Check(); + } +#endif + +#if defined(HAVE_GCCOVER) && defined(FEATURE_PREJIT) + if (GCStress::IsEnabled()) + { + // Setting up gc coverage requires the base system classes + // to be initialized. So we have deferred it until now for mscorlib. + Module *pModule = MscorlibBinder::GetModule(); + _ASSERTE(pModule->IsSystem()); + if(pModule->HasNativeImage()) + { + SetupGcCoverageForNativeImage(pModule); + } + } +#endif // defined(HAVE_GCCOVER) && !defined(FEATURE_PREJIT) +} + +/*static*/ +void SystemDomain::LoadDomain(AppDomain *pDomain) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(CheckPointer(System())); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + pDomain->SetCanUnload(); // by default can unload any domain + SystemDomain::System()->AddDomain(pDomain); +} + +ADIndex SystemDomain::GetNewAppDomainIndex(AppDomain *pAppDomain) +{ + STANDARD_VM_CONTRACT; + + DWORD count = m_appDomainIndexList.GetCount(); + DWORD i; + +#ifdef _DEBUG + if (count < 2000) + { + // So that we can keep AD index inside object header. + // We do not want to create syncblock unless needed. + i = count; + } + else + { +#endif // _DEBUG + // + // Look for an unused index. Note that in a checked build, + // we never reuse indexes - this makes it easier to tell + // when we are looking at a stale app domain. + // + + i = m_appDomainIndexList.FindElement(m_dwLowestFreeIndex, NULL); + if (i == (DWORD) ArrayList::NOT_FOUND) + i = count; + m_dwLowestFreeIndex = i+1; +#ifdef _DEBUG + if (m_dwLowestFreeIndex >= 2000) + { + m_dwLowestFreeIndex = 0; + } + } +#endif // _DEBUG + + if (i == count) + IfFailThrow(m_appDomainIndexList.Append(pAppDomain)); + else + m_appDomainIndexList.Set(i, pAppDomain); + + _ASSERTE(i < m_appDomainIndexList.GetCount()); + + // Note that index 0 means domain agile. + return ADIndex(i+1); +} + +void SystemDomain::ReleaseAppDomainIndex(ADIndex index) +{ + WRAPPER_NO_CONTRACT; + SystemDomain::LockHolder lh; + // Note that index 0 means domain agile. + index.m_dwIndex--; + + _ASSERTE(m_appDomainIndexList.Get(index.m_dwIndex) != NULL); + + m_appDomainIndexList.Set(index.m_dwIndex, NULL); + +#ifndef _DEBUG + if (index.m_dwIndex < m_dwLowestFreeIndex) + m_dwLowestFreeIndex = index.m_dwIndex; +#endif // !_DEBUG +} + +#endif // !DACCESS_COMPILE + +PTR_AppDomain SystemDomain::GetAppDomainAtIndex(ADIndex index) +{ + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + _ASSERTE(index.m_dwIndex != 0); + + PTR_AppDomain pAppDomain = TestGetAppDomainAtIndex(index); + + _ASSERTE(pAppDomain || !"Attempt to access unloaded app domain"); + + return pAppDomain; +} + +PTR_AppDomain SystemDomain::TestGetAppDomainAtIndex(ADIndex index) +{ + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + _ASSERTE(index.m_dwIndex != 0); + index.m_dwIndex--; + +#ifndef DACCESS_COMPILE + _ASSERTE(index.m_dwIndex < (DWORD)m_appDomainIndexList.GetCount()); + AppDomain *pAppDomain = (AppDomain*) m_appDomainIndexList.Get(index.m_dwIndex); +#else // DACCESS_COMPILE + PTR_ArrayListStatic pList = &m_appDomainIndexList; + AppDomain *pAppDomain = dac_cast(pList->Get(index.m_dwIndex)); +#endif // DACCESS_COMPILE + return PTR_AppDomain(pAppDomain); +} + +#ifndef DACCESS_COMPILE + +// See also code:SystemDomain::ReleaseAppDomainId +ADID SystemDomain::GetNewAppDomainId(AppDomain *pAppDomain) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + DWORD i = m_appDomainIdList.GetCount(); + + IfFailThrow(m_appDomainIdList.Append(pAppDomain)); + + _ASSERTE(i < m_appDomainIdList.GetCount()); + + return ADID(i+1); +} + +AppDomain *SystemDomain::GetAppDomainAtId(ADID index) +{ + CONTRACTL + { +#ifdef _DEBUG + if (!SystemDomain::IsUnderDomainLock() && !IsGCThread()) { MODE_COOPERATIVE;} else { DISABLED(MODE_ANY);} +#endif + GC_NOTRIGGER; + SO_TOLERANT; + NOTHROW; + } + CONTRACTL_END; + + if(index.m_dwId == 0) + return NULL; + DWORD requestedID = index.m_dwId - 1; + + if(requestedID >= (DWORD)m_appDomainIdList.GetCount()) + return NULL; + + AppDomain * result = (AppDomain *)m_appDomainIdList.Get(requestedID); + +#ifndef CROSSGEN_COMPILE + if(result==NULL && GetThread() == FinalizerThread::GetFinalizerThread() && + SystemDomain::System()->AppDomainBeingUnloaded()!=NULL && + SystemDomain::System()->AppDomainBeingUnloaded()->GetId()==index) + result=SystemDomain::System()->AppDomainBeingUnloaded(); + // If the current thread can't enter the AppDomain, then don't return it. + if (!result || !result->CanThreadEnter(GetThread())) + return NULL; +#endif // CROSSGEN_COMPILE + + return result; +} + +// Releases an appdomain index. Note that today we have code that depends on these +// indexes not being recycled, so we don't actually shrink m_appDomainIdList, but +// simply zero out an entry. THus we 'leak' the memory associated the slot in +// m_appDomainIdList. +// +// TODO make this a sparse structure so that we avoid that leak. +// +void SystemDomain::ReleaseAppDomainId(ADID index) +{ + LIMITED_METHOD_CONTRACT; + index.m_dwId--; + + _ASSERTE(index.m_dwId < (DWORD)m_appDomainIdList.GetCount()); + + m_appDomainIdList.Set(index.m_dwId, NULL); +} + +#if defined(FEATURE_COMINTEROP_APARTMENT_SUPPORT) && !defined(CROSSGEN_COMPILE) + +#ifdef _DEBUG +int g_fMainThreadApartmentStateSet = 0; +#endif + +Thread::ApartmentState SystemDomain::GetEntryPointThreadAptState(IMDInternalImport* pScope, mdMethodDef mdMethod) +{ + STANDARD_VM_CONTRACT; + + HRESULT hr; + IfFailThrow(hr = pScope->GetCustomAttributeByName(mdMethod, + DEFAULTDOMAIN_MTA_TYPE, + NULL, + NULL)); + BOOL fIsMTA = FALSE; + if(hr == S_OK) + fIsMTA = TRUE; + + IfFailThrow(hr = pScope->GetCustomAttributeByName(mdMethod, + DEFAULTDOMAIN_STA_TYPE, + NULL, + NULL)); + BOOL fIsSTA = FALSE; + if (hr == S_OK) + fIsSTA = TRUE; + + if (fIsSTA && fIsMTA) + COMPlusThrowHR(COR_E_CUSTOMATTRIBUTEFORMAT); + + if (fIsSTA) + return Thread::AS_InSTA; + else if (fIsMTA) + return Thread::AS_InMTA; + + return Thread::AS_Unknown; +} + +void SystemDomain::SetThreadAptState (IMDInternalImport* pScope, Thread::ApartmentState state) +{ + STANDARD_VM_CONTRACT; + + BOOL fIsLegacy = FALSE; + + // Check for legacy behavior regarding COM Apartment state of the main thread. + +#define METAMODEL_MAJOR_VER_WITH_NEW_BEHAVIOR 2 +#define METAMODEL_MINOR_VER_WITH_NEW_BEHAVIOR 0 + + LPCSTR pVer; + IfFailThrow(pScope->GetVersionString(&pVer)); + + // Does this look like a version? + if (pVer != NULL) + { + // Is it 'vN.' where N is a digit? + if ((pVer[0] == 'v' || pVer[0] == 'V') && + IS_DIGIT(pVer[1]) && + (pVer[2] == '.') ) + { + // Looks like a version. Is it lesser than v2.0 major version where we start using new behavior? + fIsLegacy = DIGIT_TO_INT(pVer[1]) < METAMODEL_MAJOR_VER_WITH_NEW_BEHAVIOR; + } + } + + if (!fIsLegacy && g_pConfig != NULL) + { + fIsLegacy = g_pConfig->LegacyApartmentInitPolicy(); + } + + + Thread* pThread = GetThread(); + _ASSERTE(pThread); + + if(state == Thread::AS_InSTA) + { + Thread::ApartmentState pState = pThread->SetApartment(Thread::AS_InSTA, TRUE); + _ASSERTE(pState == Thread::AS_InSTA); + } + else if ((state == Thread::AS_InMTA) || (!fIsLegacy)) + { + // If either MTAThreadAttribute is specified or (if no attribute is specified and we are not + // running in legacy mode), then + // we will set the apartment state to MTA. The reason for this is to ensure the apartment + // state is consistent and reliably set. Without this, the apartment state for the main + // thread would be undefined and would actually be dependent on if the assembly was + // ngen'd, which other type were loaded, etc. + Thread::ApartmentState pState = pThread->SetApartment(Thread::AS_InMTA, TRUE); + _ASSERTE(pState == Thread::AS_InMTA); + } + +#ifdef _DEBUG + g_fMainThreadApartmentStateSet++; +#endif +} +#endif // defined(FEATURE_COMINTEROP_APARTMENT_SUPPORT) && !defined(CROSSGEN_COMPILE) + +// Looks in all the modules for the DefaultDomain attribute +// The order is assembly and then the modules. It is first +// come, first serve. +BOOL SystemDomain::SetGlobalSharePolicyUsingAttribute(IMDInternalImport* pScope, mdMethodDef mdMethod) +{ + STANDARD_VM_CONTRACT; + +#ifdef FEATURE_FUSION + HRESULT hr; + + // + // Check to see if the assembly has the LoaderOptimization attribute set. + // + + DWORD cbVal; + BYTE *pVal; + IfFailThrow(hr = pScope->GetCustomAttributeByName(mdMethod, + DEFAULTDOMAIN_LOADEROPTIMIZATION_TYPE, + (const void**)&pVal, &cbVal)); + + if (hr == S_OK) { + CustomAttributeParser cap(pVal, cbVal); + IfFailThrow(cap.SkipProlog()); + + UINT8 u1; + IfFailThrow(cap.GetU1(&u1)); + + g_dwGlobalSharePolicy = u1 & AppDomain::SHARE_POLICY_MASK; + + return TRUE; + } +#endif + + return FALSE; +} + +void SystemDomain::SetupDefaultDomain() +{ + CONTRACT_VOID + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACT_END; + + + Thread *pThread = GetThread(); + _ASSERTE(pThread); + + AppDomain *pDomain; + pDomain = pThread->GetDomain(); + _ASSERTE(pDomain); + + GCX_COOP(); + + ENTER_DOMAIN_PTR(SystemDomain::System()->DefaultDomain(),ADV_DEFAULTAD) + { + // Push this frame around loading the main assembly to ensure the + // debugger can properly recgonize any managed code that gets run + // as "class initializaion" code. + FrameWithCookie __dcimf; + + { + GCX_PREEMP(); + InitializeDefaultDomain(TRUE); + } + + __dcimf.Pop(); + } + END_DOMAIN_TRANSITION; + + RETURN; +} + +HRESULT SystemDomain::SetupDefaultDomainNoThrow() +{ + CONTRACTL + { + NOTHROW; + MODE_ANY; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + + EX_TRY + { + SystemDomain::SetupDefaultDomain(); + } + EX_CATCH_HRESULT(hr); + + return hr; +} + +#ifdef _DEBUG +int g_fInitializingInitialAD = 0; +#endif + +// This routine completes the initialization of the default domaine. +// After this call mananged code can be executed. +void SystemDomain::InitializeDefaultDomain( + BOOL allowRedirects, + ICLRPrivBinder * pBinder) +{ + STANDARD_VM_CONTRACT; + + WCHAR* pwsConfig = NULL; + WCHAR* pwsPath = NULL; + + ETWOnStartup (InitDefaultDomain_V1, InitDefaultDomainEnd_V1); + +#if defined(FEATURE_FUSION) // SxS + // Determine the application base and the configuration file name + CQuickWSTR sPathName; + CQuickWSTR sConfigName; + + SIZE_T dwSize; + HRESULT hr = GetConfigFileFromWin32Manifest(sConfigName.Ptr(), + sConfigName.MaxSize(), + &dwSize); + if(FAILED(hr)) + { + if(hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) + { + sConfigName.ReSizeThrows(dwSize); + hr = GetConfigFileFromWin32Manifest(sConfigName.Ptr(), + sConfigName.MaxSize(), + &dwSize); + } + IfFailThrow(hr); + } + else + sConfigName.ReSizeThrows(dwSize); + + hr = GetApplicationPathFromWin32Manifest(sPathName.Ptr(), + sPathName.MaxSize(), + &dwSize); + if(FAILED(hr)) + { + if(hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) + { + sPathName.ReSizeThrows(dwSize); + hr = GetApplicationPathFromWin32Manifest(sPathName.Ptr(), + sPathName.MaxSize(), + &dwSize); + } + IfFailThrow(hr); + } + else + sPathName.ReSizeThrows(dwSize); + + pwsConfig = (sConfigName.Size() > 0 ? sConfigName.Ptr() : NULL); + pwsPath = (sPathName.Size() > 0 ? sPathName.Ptr() : NULL); +#endif // defined(FEATURE_FUSION) // SxS + + // Setup the default AppDomain. + +#ifdef _DEBUG + g_fInitializingInitialAD++; +#endif + + AppDomain* pDefaultDomain = SystemDomain::System()->DefaultDomain(); + + if (pBinder != nullptr) + { + pDefaultDomain->SetLoadContextHostBinder(pBinder); + } + #ifdef FEATURE_APPX_BINDER + else if (AppX::IsAppXProcess()) + { + CLRPrivBinderAppX * pAppXBinder = CLRPrivBinderAppX::GetOrCreateBinder(); + pDefaultDomain->SetLoadContextHostBinder(pAppXBinder); + } + #endif + + { + GCX_COOP(); + +#ifndef CROSSGEN_COMPILE + if (!NingenEnabled()) + { +#ifndef FEATURE_CORECLR + pDefaultDomain->InitializeHashing(NULL); + pDefaultDomain->InitializeSorting(NULL); +#endif // FEATURE_CORECLR + } +#endif // CROSSGEN_COMPILE + + pDefaultDomain->InitializeDomainContext(allowRedirects, pwsPath, pwsConfig); + +#ifndef CROSSGEN_COMPILE + if (!NingenEnabled()) + { +#ifdef FEATURE_CLICKONCE + pDefaultDomain->InitializeDefaultClickOnceDomain(); +#endif // FEATURE_CLICKONCE + + if (!IsSingleAppDomain()) + { + pDefaultDomain->InitializeDefaultDomainManager(); + pDefaultDomain->InitializeDefaultDomainSecurity(); + } + } +#endif // CROSSGEN_COMPILE + } + + // DefaultDomain Load event + ETW::LoaderLog::DomainLoad(pDefaultDomain); + +#ifdef _DEBUG + g_fInitializingInitialAD--; +#endif + + TESTHOOKCALL(RuntimeStarted(RTS_DEFAULTADREADY)); +} + + + +#ifndef CROSSGEN_COMPILE + +#ifdef _DEBUG +Volatile g_fInExecuteMainMethod = 0; +#endif + +#ifndef FEATURE_CORECLR +void SystemDomain::ExecuteMainMethod(HMODULE hMod, __in_opt LPWSTR path /*=NULL*/) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + PRECONDITION(CheckPointer(hMod, NULL_OK)); + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + +#ifdef _DEBUG + CounterHolder counter(&g_fInExecuteMainMethod); +#endif + + Thread *pThread = GetThread(); + _ASSERTE(pThread); + + GCX_COOP(); + + // + // There is no EH protecting this transition! + // This is generically ok in this method because if we throw out of here, it becomes unhandled anyway. + // + FrameWithCookie frame; + pThread->EnterContextRestricted(SystemDomain::System()->DefaultDomain()->GetDefaultContext(), &frame); + _ASSERTE(pThread->GetDomain()); + + AppDomain *pDomain = GetAppDomain(); + _ASSERTE(pDomain); + + // Push this frame around loading the main assembly to ensure the + // debugger can properly recognize any managed code that gets run + // as "class initializaion" code. + FrameWithCookie __dcimf; + { + GCX_PREEMP(); + + PEImageHolder pTempImage(PEImage::LoadImage(hMod)); + + PEFileHolder pTempFile(PEFile::Open(pTempImage.Extract())); + + // Check for CustomAttributes - Set up the DefaultDomain and the main thread + // Note that this has to be done before ExplicitBind() as it + // affects the bind + mdToken tkEntryPoint = pTempFile->GetEntryPointToken(); + // @TODO: What if the entrypoint is in another file of the assembly? + ReleaseHolder scope(pTempFile->GetMDImportWithRef()); + // In theory, we should have a valid executable image and scope should never be NULL, but we've been + // getting Watson failures for AVs here due to ISVs modifying image headers and some new OS loader + // checks (see Dev10# 718530 and Windows 7# 615596) + if (scope == NULL) + { + ThrowHR(COR_E_BADIMAGEFORMAT); + } + +#ifdef FEATURE_COMINTEROP + Thread::ApartmentState state = Thread::AS_Unknown; + + if((!IsNilToken(tkEntryPoint)) && (TypeFromToken(tkEntryPoint) == mdtMethodDef)) { + if (scope->IsValidToken(tkEntryPoint)) + state = SystemDomain::GetEntryPointThreadAptState(scope, tkEntryPoint); + else + ThrowHR(COR_E_BADIMAGEFORMAT); + } + + // If the entry point has an explicit thread apartment state, set it + // before running the AppDomainManager initialization code. + if (state == Thread::AS_InSTA || state == Thread::AS_InMTA) + SystemDomain::SetThreadAptState(scope, state); +#endif // FEATURE_COMINTEROP + + BOOL fSetGlobalSharePolicyUsingAttribute = FALSE; + + if((!IsNilToken(tkEntryPoint)) && (TypeFromToken(tkEntryPoint) == mdtMethodDef)) + { + // The global share policy needs to be set before initializing default domain + // so that it is in place for loading of appdomain manager. + fSetGlobalSharePolicyUsingAttribute = SystemDomain::SetGlobalSharePolicyUsingAttribute(scope, tkEntryPoint); + } + + // This can potentially run managed code. + InitializeDefaultDomain(FALSE); + +#ifdef FEATURE_COMINTEROP + // If we haven't set an explicit thread apartment state, set it after the + // AppDomainManager has got a chance to go set it in InitializeNewDomain. + if (state != Thread::AS_InSTA && state != Thread::AS_InMTA) + SystemDomain::SetThreadAptState(scope, state); +#endif // FEATURE_COMINTEROP + + if (fSetGlobalSharePolicyUsingAttribute) + SystemDomain::System()->DefaultDomain()->SetupLoaderOptimization(g_dwGlobalSharePolicy); + + NewHolder pSecDesc(Security::CreatePEFileSecurityDescriptor(pDomain, pTempFile)); + + { + GCX_COOP(); + pSecDesc->Resolve(); + if (pSecDesc->AllowBindingRedirects()) + pDomain->TurnOnBindingRedirects(); + } + + PEAssemblyHolder pFile(pDomain->BindExplicitAssembly(hMod, TRUE)); + + pDomain->m_pRootAssembly = GetAppDomain()->LoadAssembly(NULL, pFile, FILE_ACTIVE); + + { + GCX_COOP(); + + // Reuse the evidence that was generated for the PEFile for the assembly so we don't have to + // regenerate evidence of the same type again if it is requested later. + pDomain->m_pRootAssembly->GetSecurityDescriptor()->SetEvidenceFromPEFile(pSecDesc); + } + + // If the AppDomainManager for the default domain was specified in the application config file then + // we require that the assembly be trusted in order to set the manager + if (pDomain->HasAppDomainManagerInfo() && pDomain->AppDomainManagerSetFromConfig()) + { + Assembly *pEntryAssembly = pDomain->GetAppDomainManagerEntryAssembly(); + if (!pEntryAssembly->GetSecurityDescriptor()->AllowApplicationSpecifiedAppDomainManager()) + { + COMPlusThrow(kTypeLoadException, IDS_E_UNTRUSTED_APPDOMAIN_MANAGER); + } + } + + if (CorCommandLine::m_pwszAppFullName == NULL) { + StackSString friendlyName; + StackSString assemblyPath = pFile->GetPath(); + SString::Iterator i = assemblyPath.End(); + + if (PEAssembly::FindLastPathSeparator(assemblyPath, i)) { + i++; + friendlyName.Set(assemblyPath, i, assemblyPath.End()); + } + else + friendlyName.Set(assemblyPath); + + pDomain->SetFriendlyName(friendlyName, TRUE); + } + } + __dcimf.Pop(); + + { + GCX_PREEMP(); + + LOG((LF_CLASSLOADER | LF_CORDB, + LL_INFO10, + "Created domain for an executable at %p\n", + (pDomain->m_pRootAssembly ? pDomain->m_pRootAssembly->Parent() : NULL))); + TESTHOOKCALL(RuntimeStarted(RTS_CALLINGENTRYPOINT)); + +#ifdef FEATURE_MULTICOREJIT + pDomain->GetMulticoreJitManager().AutoStartProfile(pDomain); +#endif + + pDomain->m_pRootAssembly->ExecuteMainMethod(NULL, TRUE /* waitForOtherThreads */); + } + + pThread->ReturnToContext(&frame); + +#ifdef FEATURE_TESTHOOKS + TESTHOOKCALL(LeftAppDomain(DefaultADID)); +#endif +} +#endif //!FEATURE_CORECLR + +#ifdef FEATURE_CLICKONCE +void SystemDomain::ActivateApplication(int *pReturnValue) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + struct _gc { + OBJECTREF orThis; + } gc; + ZeroMemory(&gc, sizeof(gc)); + + GCX_COOP(); + GCPROTECT_BEGIN(gc); + + gc.orThis = SystemDomain::System()->DefaultDomain()->GetExposedObject(); + + MethodDescCallSite activateApp(METHOD__APP_DOMAIN__ACTIVATE_APPLICATION, &gc.orThis); + + ARG_SLOT args[] = { + ObjToArgSlot(gc.orThis), + }; + int retval = activateApp.Call_RetI4(args); + if (pReturnValue) + *pReturnValue = retval; + + GCPROTECT_END(); +} +#endif // FEATURE_CLICKONCE + +#ifdef FEATURE_MIXEDMODE +static HRESULT RunDllMainHelper(HINSTANCE hInst, DWORD dwReason, LPVOID lpReserved, Thread* pThread, bool bReenablePreemptive) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_COOPERATIVE; + STATIC_CONTRACT_FAULT; + + MethodDesc *pMD; + AppDomain *pDomain; + Module *pModule; + HRESULT hr = S_FALSE; // Assume no entry point. + + // Setup the thread state to cooperative to run managed code. + + // Get the old domain from the thread. Legacy dll entry points must always + // be run from the default domain. + // + // We cannot support legacy dlls getting loaded into all domains!! + EX_TRY + { + ENTER_DOMAIN_PTR(SystemDomain::System()->DefaultDomain(),ADV_DEFAULTAD) + { + pDomain = pThread->GetDomain(); + + // The module needs to be in the current list if you are coming here. + pModule = pDomain->GetIJWModule(hInst); + if (!pModule) + goto ErrExit; + + // See if there even is an entry point. + pMD = pModule->GetDllEntryPoint(); + if (!pMD) + goto ErrExit; + + // We're actually going to run some managed code. There may be a customer + // debug probe enabled, that prevents execution in the loader lock. + CanRunManagedCode(hInst); + + { + // Enter cooperative mode + GCX_COOP_NO_DTOR(); + } + + // Run through the helper which will do exception handling for us. + hr = ::RunDllMain(pMD, hInst, dwReason, lpReserved); + + { + // Update thread state for the case where we are returning to unmanaged code. + GCX_MAYBE_PREEMP_NO_DTOR(bReenablePreemptive); + } + +ErrExit: ; + // does not throw exception + } + END_DOMAIN_TRANSITION; + + } + EX_CATCH + { + hr = GetExceptionHResult(GET_THROWABLE()); + } + EX_END_CATCH(SwallowAllExceptions) + + return (hr); +} + +//***************************************************************************** +// This guy will set up the proper thread state, look for the module given +// the hinstance, and then run the entry point if there is one. +//***************************************************************************** +HRESULT SystemDomain::RunDllMain(HINSTANCE hInst, DWORD dwReason, LPVOID lpReserved) +{ + + CONTRACTL + { + NOTHROW; + if (GetThread() && !lpReserved) {MODE_PREEMPTIVE;} else {DISABLED(MODE_PREEMPTIVE);}; + if(GetThread()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);}; + } + CONTRACTL_END; + + + Thread *pThread = NULL; + BOOL fEnterCoop = FALSE; + HRESULT hr = S_FALSE; // Assume no entry point. + + pThread = GetThread(); + if ((!pThread && (dwReason == DLL_PROCESS_DETACH || dwReason == DLL_THREAD_DETACH)) || + g_fEEShutDown) + return S_OK; + + // ExitProcess is called while a thread is doing GC. + if (dwReason == DLL_PROCESS_DETACH && GCHeap::IsGCInProgress()) + return S_OK; + + // ExitProcess is called on a thread that we don't know about + if (dwReason == DLL_PROCESS_DETACH && GetThread() == NULL) + return S_OK; + + // Need to setup the thread since this might be the first time the EE has + // seen it if the thread was created in unmanaged code and this is a thread + // attach event. + if (pThread) + fEnterCoop = pThread->PreemptiveGCDisabled(); + else { + pThread = SetupThreadNoThrow(&hr); + if (pThread == NULL) + return hr; + } + + return RunDllMainHelper(hInst, dwReason, lpReserved, pThread, !fEnterCoop); +} +#endif // FEATURE_MIXEDMODE + +#endif // CROSSGEN_COMPILE + + + +// Helper function to load an assembly. This is called from LoadCOMClass. +/* static */ + +Assembly *AppDomain::LoadAssemblyHelper(LPCWSTR wszAssembly, + LPCWSTR wszCodeBase) +{ + CONTRACT(Assembly *) + { + THROWS; + POSTCONDITION(CheckPointer(RETVAL)); + PRECONDITION(wszAssembly || wszCodeBase); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACT_END; + + AssemblySpec spec; + if(wszAssembly) { + #define MAKE_TRANSLATIONFAILED { ThrowOutOfMemory(); } + MAKE_UTF8PTR_FROMWIDE(szAssembly,wszAssembly); + #undef MAKE_TRANSLATIONFAILED + + IfFailThrow(spec.Init(szAssembly)); + } + + if (wszCodeBase) { + spec.SetCodeBase(wszCodeBase); + } + RETURN spec.LoadAssembly(FILE_LOADED); +} + +#if defined(FEATURE_CLASSIC_COMINTEROP) && !defined(CROSSGEN_COMPILE) + +#ifdef FEATURE_CORECLR +MethodTable *AppDomain::LoadCOMClass(GUID clsid, + BOOL bLoadRecord/*=FALSE*/, + BOOL* pfAssemblyInReg/*=NULL*/) +{ + // @CORESYSTODO: what to do here? + return NULL; +} +#else // FEATURE_CORECLR + +static BOOL IsSameRuntimeVersion(ICLRRuntimeInfo *pInfo1, ICLRRuntimeInfo *pInfo2) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + WCHAR wszVersion1[_MAX_PATH]; + WCHAR wszVersion2[_MAX_PATH]; + DWORD cchVersion; + + cchVersion = COUNTOF(wszVersion1); + IfFailThrow(pInfo1->GetVersionString(wszVersion1, &cchVersion)); + + cchVersion = COUNTOF(wszVersion2); + IfFailThrow(pInfo2->GetVersionString(wszVersion2, &cchVersion)); + + return SString::_wcsicmp(wszVersion1, wszVersion2) == 0; +} + +MethodTable *AppDomain::LoadCOMClass(GUID clsid, + BOOL bLoadRecord/*=FALSE*/, + BOOL* pfAssemblyInReg/*=NULL*/) +{ + CONTRACT (MethodTable*) + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACT_END; + + + MethodTable* pMT = NULL; + + NewArrayHolder wszClassName = NULL; + NewArrayHolder wszAssemblyString = NULL; + NewArrayHolder wszCodeBaseString = NULL; + + DWORD cbAssembly = 0; + DWORD cbCodeBase = 0; + Assembly *pAssembly = NULL; + BOOL fFromRegistry = FALSE; + BOOL fRegFreePIA = FALSE; + + HRESULT hr = S_OK; + + if (pfAssemblyInReg != NULL) + *pfAssemblyInReg = FALSE; + + // with sxs.dll help + hr = FindShimInfoFromWin32(clsid, bLoadRecord, NULL, NULL, &wszClassName, &wszAssemblyString, &fRegFreePIA); + + if(FAILED(hr)) + { + hr = FindShimInfoFromRegistry(clsid, bLoadRecord, VER_ASSEMBLYMAJORVERSION, VER_ASSEMBLYMINORVERSION, + &wszClassName, &wszAssemblyString, &wszCodeBaseString); + if (FAILED(hr)) + RETURN NULL; + + fFromRegistry = TRUE; + } + + // Skip the GetRuntimeForManagedCOMObject check for value types since they cannot be activated and are + // always used for wrapping existing instances coming from COM. + if (!bLoadRecord) + { + // We will load the assembly only if it is a PIA or if unmanaged activation would load the currently running + // runtime. Otherwise we return NULL which will result in using the default System.__ComObject type. + + // the type is a PIA type if mscoree.dll is not its inproc server dll or it was specified as in the manifest + BOOL fPIA = (fFromRegistry ? !Clr::Util::Com::CLSIDHasMscoreeAsInprocServer32(clsid) : fRegFreePIA); + if (!fPIA) + { + // this isn't a PIA, so we must determine which runtime it would load + ReleaseHolder pRuntimeHostInternal; + IfFailThrow(g_pCLRRuntime->GetInterface(CLSID_CLRRuntimeHostInternal, + IID_ICLRRuntimeHostInternal, + &pRuntimeHostInternal)); + + // we call the shim to see which runtime would this be activated in + ReleaseHolder pRuntimeInfo; + if (FAILED(pRuntimeHostInternal->GetRuntimeForManagedCOMObject(clsid, IID_ICLRRuntimeInfo, &pRuntimeInfo))) + { + // the requested runtime is not loadable - don't load the assembly + RETURN NULL; + } + + if (!IsSameRuntimeVersion(g_pCLRRuntime, pRuntimeInfo)) + { + // the requested runtime is different from this runtime - don't load the assembly + RETURN NULL; + } + } + } + + if (pfAssemblyInReg != NULL) + *pfAssemblyInReg = TRUE; + + if (wszAssemblyString != NULL) { + pAssembly = LoadAssemblyHelper(wszAssemblyString, wszCodeBaseString); + pMT = TypeName::GetTypeFromAssembly(wszClassName, pAssembly).GetMethodTable(); + if (!pMT) + goto ErrExit; + } + + if (pMT == NULL) { + ErrExit: + // Convert the GUID to its string representation. + WCHAR szClsid[64]; + if (GuidToLPWSTR(clsid, szClsid, NumItems(szClsid)) == 0) + szClsid[0] = 0; + + // Throw an exception indicating we failed to load the type with + // the requested CLSID. + COMPlusThrow(kTypeLoadException, IDS_CLASSLOAD_NOCLSIDREG, szClsid); + } + + RETURN pMT; +} + +#endif // FEATURE_CORECLR + +#endif // FEATURE_CLASSIC_COMINTEROP && !CROSSGEN_COMPILE + + +/*static*/ +bool SystemDomain::IsReflectionInvocationMethod(MethodDesc* pMeth) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + MethodTable* pCaller = pMeth->GetMethodTable(); + + // All Reflection Invocation methods are defined in mscorlib.dll + if (!pCaller->GetModule()->IsSystem()) + return false; + + /* List of types that should be skipped to identify true caller */ + static const BinderClassID reflectionInvocationTypes[] = { + CLASS__METHOD, + CLASS__METHOD_BASE, + CLASS__METHOD_INFO, + CLASS__CONSTRUCTOR, + CLASS__CONSTRUCTOR_INFO, + CLASS__CLASS, + CLASS__TYPE_HANDLE, + CLASS__METHOD_HANDLE, + CLASS__FIELD_HANDLE, + CLASS__TYPE, + CLASS__FIELD, + CLASS__RT_FIELD_INFO, + CLASS__FIELD_INFO, + CLASS__EVENT, + CLASS__EVENT_INFO, + CLASS__PROPERTY, + CLASS__PROPERTY_INFO, + CLASS__ACTIVATOR, + CLASS__ARRAY, + CLASS__ASSEMBLYBASE, + CLASS__ASSEMBLY, + CLASS__TYPE_DELEGATOR, + CLASS__RUNTIME_HELPERS, +#if defined(FEATURE_COMINTEROP) && !defined(FEATURE_CORECLR) + CLASS__ITYPE, + CLASS__IASSEMBLY, + CLASS__IMETHODBASE, + CLASS__IMETHODINFO, + CLASS__ICONSTRUCTORINFO, + CLASS__IFIELDINFO, + CLASS__IPROPERTYINFO, + CLASS__IEVENTINFO, + CLASS__IAPPDOMAIN, +#endif // FEATURE_COMINTEROP && !FEATURE_CORECLR + CLASS__LAZY_INITIALIZER, + CLASS__DYNAMICMETHOD, + CLASS__DELEGATE, + CLASS__MULTICAST_DELEGATE + }; + + static const BinderClassID genericReflectionInvocationTypes[] = { + CLASS__LAZY_HELPERS, + CLASS__LAZY + }; + + static mdTypeDef genericReflectionInvocationTypeDefs[NumItems(genericReflectionInvocationTypes)]; + + static bool fInited = false; + + if (!VolatileLoad(&fInited)) + { + // Make sure all types are loaded so that we can use faster GetExistingClass() + for (unsigned i = 0; i < NumItems(reflectionInvocationTypes); i++) + { + MscorlibBinder::GetClass(reflectionInvocationTypes[i]); + } + + // Make sure all types are loaded so that we can use faster GetExistingClass() + for (unsigned i = 0; i < NumItems(genericReflectionInvocationTypes); i++) + { + genericReflectionInvocationTypeDefs[i] = MscorlibBinder::GetClass(genericReflectionInvocationTypes[i])->GetCl(); + } + + MscorlibBinder::GetClass(CLASS__APP_DOMAIN); + + VolatileStore(&fInited, true); + } + + if (pCaller->HasInstantiation()) + { + // For generic types, pCaller will be an instantiated type and never equal to the type definition. + // So we compare their TypeDef tokens instead. + for (unsigned i = 0; i < NumItems(genericReflectionInvocationTypeDefs); i++) + { + if (pCaller->GetCl() == genericReflectionInvocationTypeDefs[i]) + return true; + } + } + else + { + for (unsigned i = 0; i < NumItems(reflectionInvocationTypes); i++) + { + if (MscorlibBinder::GetExistingClass(reflectionInvocationTypes[i]) == pCaller) + return true; + } + + // AppDomain is an example of a type that is both used in the implementation of + // reflection, and also a type that contains methods that are clients of reflection + // (i.e., they instigate their own CreateInstance). Skip all AppDomain frames that + // are NOT known clients of reflection. NOTE: The ever-increasing complexity of this + // exclusion list is a sign that we need a better way--this is error-prone and + // unmaintainable as more changes are made to BCL types. + if ((pCaller == MscorlibBinder::GetExistingClass(CLASS__APP_DOMAIN)) + && (pMeth != MscorlibBinder::GetMethod(METHOD__APP_DOMAIN__CREATE_APP_DOMAIN_MANAGER)) // This uses reflection to create an AppDomainManager + #ifdef FEATURE_CLICKONCE + && (pMeth != MscorlibBinder::GetMethod(METHOD__APP_DOMAIN__ACTIVATE_APPLICATION)) // This uses reflection to create an ActivationContext + #endif + ) + { + return true; + } + } + + return false; +} + +#ifndef CROSSGEN_COMPILE +struct CallersDataWithStackMark +{ + StackCrawlMark* stackMark; + BOOL foundMe; +#ifdef FEATURE_REMOTING + BOOL skippingRemoting; +#endif + MethodDesc* pFoundMethod; + MethodDesc* pPrevMethod; + AppDomain* pAppDomain; +}; + +/*static*/ +MethodDesc* SystemDomain::GetCallersMethod(StackCrawlMark* stackMark, + AppDomain **ppAppDomain/*=NULL*/) + +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + GCX_COOP(); + + CallersDataWithStackMark cdata; + ZeroMemory(&cdata, sizeof(CallersDataWithStackMark)); + cdata.stackMark = stackMark; + + GetThread()->StackWalkFrames(CallersMethodCallbackWithStackMark, &cdata, FUNCTIONSONLY | LIGHTUNWIND); + + if(cdata.pFoundMethod) { + if (ppAppDomain) + *ppAppDomain = cdata.pAppDomain; + return cdata.pFoundMethod; + } else + return NULL; +} + +/*static*/ +MethodTable* SystemDomain::GetCallersType(StackCrawlMark* stackMark, + AppDomain **ppAppDomain/*=NULL*/) + +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + CallersDataWithStackMark cdata; + ZeroMemory(&cdata, sizeof(CallersDataWithStackMark)); + cdata.stackMark = stackMark; + + GetThread()->StackWalkFrames(CallersMethodCallbackWithStackMark, &cdata, FUNCTIONSONLY | LIGHTUNWIND); + + if(cdata.pFoundMethod) { + if (ppAppDomain) + *ppAppDomain = cdata.pAppDomain; + return cdata.pFoundMethod->GetMethodTable(); + } else + return NULL; +} + +/*static*/ +Module* SystemDomain::GetCallersModule(StackCrawlMark* stackMark, + AppDomain **ppAppDomain/*=NULL*/) + +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + GCX_COOP(); + + CallersDataWithStackMark cdata; + ZeroMemory(&cdata, sizeof(CallersDataWithStackMark)); + cdata.stackMark = stackMark; + + GetThread()->StackWalkFrames(CallersMethodCallbackWithStackMark, &cdata, FUNCTIONSONLY | LIGHTUNWIND); + + if(cdata.pFoundMethod) { + if (ppAppDomain) + *ppAppDomain = cdata.pAppDomain; + return cdata.pFoundMethod->GetModule(); + } else + return NULL; +} + +struct CallersData +{ + int skip; + MethodDesc* pMethod; +}; + +/*static*/ +Assembly* SystemDomain::GetCallersAssembly(StackCrawlMark *stackMark, + AppDomain **ppAppDomain/*=NULL*/) +{ + WRAPPER_NO_CONTRACT; + Module* mod = GetCallersModule(stackMark, ppAppDomain); + if (mod) + return mod->GetAssembly(); + return NULL; +} + +/*static*/ +Module* SystemDomain::GetCallersModule(int skip) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + GCX_COOP(); + + CallersData cdata; + ZeroMemory(&cdata, sizeof(CallersData)); + cdata.skip = skip; + + StackWalkFunctions(GetThread(), CallersMethodCallback, &cdata); + + if(cdata.pMethod) + return cdata.pMethod->GetModule(); + else + return NULL; +} + +/*private static*/ +StackWalkAction SystemDomain::CallersMethodCallbackWithStackMark(CrawlFrame* pCf, VOID* data) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + SO_INTOLERANT; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + + MethodDesc *pFunc = pCf->GetFunction(); + + /* We asked to be called back only for functions */ + _ASSERTE(pFunc); + + CallersDataWithStackMark* pCaller = (CallersDataWithStackMark*) data; + if (pCaller->stackMark) + { + if (!pCf->IsInCalleesFrames(pCaller->stackMark)) + { + // save the current in case it is the one we want + pCaller->pPrevMethod = pFunc; + pCaller->pAppDomain = pCf->GetAppDomain(); + return SWA_CONTINUE; + } + + // LookForMe stack crawl marks needn't worry about reflection or + // remoting frames on the stack. Each frame above (newer than) the + // target will be captured by the logic above. Once we transition to + // finding the stack mark below the AofRA, we know that we hit the + // target last time round and immediately exit with the cached result. + + if (*(pCaller->stackMark) == LookForMe) + { + pCaller->pFoundMethod = pCaller->pPrevMethod; + return SWA_ABORT; + } + } + + // Skip reflection and remoting frames that could lie between a stack marked + // method and its true caller (or that caller and its own caller). These + // frames are infrastructure and logically transparent to the stack crawling + // algorithm. + + // Skipping remoting frames. We always skip entire client to server spans + // (though we see them in the order server then client during a stack crawl + // obviously). + + // We spot the server dispatcher end because all calls are dispatched + // through a single method: StackBuilderSink._PrivateProcessMessage. + + Frame* frame = pCf->GetFrame(); + _ASSERTE(pCf->IsFrameless() || frame); + +#ifdef FEATURE_REMOTING + if (pFunc == MscorlibBinder::GetMethod(METHOD__STACK_BUILDER_SINK__PRIVATE_PROCESS_MESSAGE)) + { + _ASSERTE(!pCaller->skippingRemoting); + pCaller->skippingRemoting = true; + return SWA_CONTINUE; + } + // And we spot the client end because there's a transparent proxy transition + // frame pushed. + if (frame && frame->GetFrameType() == Frame::TYPE_TP_METHOD_FRAME) + { + pCaller->skippingRemoting = false; + return SWA_CONTINUE; + } + + // Skip any frames into between the server and client remoting endpoints. + if (pCaller->skippingRemoting) + return SWA_CONTINUE; +#endif + + + // Skipping reflection frames. We don't need to be quite as exhaustive here + // as the security or reflection stack walking code since we know this logic + // is only invoked for selected methods in mscorlib itself. So we're + // reasonably sure we won't have any sensitive methods late bound invoked on + // constructors, properties or events. This leaves being invoked via + // MethodInfo, Type or Delegate (and depending on which invoke overload is + // being used, several different reflection classes may be involved). + + g_IBCLogger.LogMethodDescAccess(pFunc); + + if (SystemDomain::IsReflectionInvocationMethod(pFunc)) + return SWA_CONTINUE; + + if (frame && frame->GetFrameType() == Frame::TYPE_MULTICAST) + { + // This must be either a secure delegate frame or a true multicast delegate invocation. + + _ASSERTE(pFunc->GetMethodTable()->IsDelegate()); + + DELEGATEREF del = (DELEGATEREF)((SecureDelegateFrame*)frame)->GetThis(); // This can throw. + + if (COMDelegate::IsSecureDelegate(del)) + { + if (del->IsWrapperDelegate()) + { + // On ARM, we use secure delegate infrastructure to preserve R4 register. + return SWA_CONTINUE; + } + // For a secure delegate frame, we should return the delegate creator instead + // of the delegate method itself. + pFunc = (MethodDesc*) del->GetMethodPtrAux(); + } + else + { + _ASSERTE(COMDelegate::IsTrueMulticastDelegate(del)); + return SWA_CONTINUE; + } + } + + // Return the first non-reflection/remoting frame if no stack mark was + // supplied. + if (!pCaller->stackMark) + { + pCaller->pFoundMethod = pFunc; + pCaller->pAppDomain = pCf->GetAppDomain(); + return SWA_ABORT; + } + + // If we got here, we must already be in the frame containing the stack mark and we are not looking for "me". + _ASSERTE(pCaller->stackMark && + pCf->IsInCalleesFrames(pCaller->stackMark) && + *(pCaller->stackMark) != LookForMe); + + // When looking for caller's caller, we delay returning results for another + // round (the way this is structured, we will still be able to skip + // reflection and remoting frames between the caller and the caller's + // caller). + + if ((*(pCaller->stackMark) == LookForMyCallersCaller) && + (pCaller->pFoundMethod == NULL)) + { + pCaller->pFoundMethod = pFunc; + return SWA_CONTINUE; + } + +#ifndef FEATURE_REMOTING + // If remoting is not available, we only set the caller if the crawlframe is from the same domain. + // Why? Because if the callerdomain is different from current domain, + // there have to be interop/native frames in between. + // For example, in the CORECLR, if we find the caller to be in a different domain, then the + // call into reflection is due to an unmanaged call into mscorlib. For that + // case, the caller really is an INTEROP method. + // In general, if the caller is INTEROP, we set the caller/callerdomain to be NULL + // (To be precise: they are already NULL and we don't change them). + if (pCf->GetAppDomain() == GetAppDomain()) +#endif // FEATURE_REMOTING + // We must either be looking for the caller, or the caller's caller when + // we've already found the caller (we used a non-null value in pFoundMethod + // simply as a flag, the correct method to return in both case is the + // current method). + { + pCaller->pFoundMethod = pFunc; + pCaller->pAppDomain = pCf->GetAppDomain(); + } + + return SWA_ABORT; +} + +/*private static*/ +StackWalkAction SystemDomain::CallersMethodCallback(CrawlFrame* pCf, VOID* data) +{ + LIMITED_METHOD_CONTRACT; + STATIC_CONTRACT_SO_TOLERANT; + MethodDesc *pFunc = pCf->GetFunction(); + + /* We asked to be called back only for functions */ + _ASSERTE(pFunc); + + // Ignore intercepted frames + if(pFunc->IsInterceptedForDeclSecurity()) + return SWA_CONTINUE; + + CallersData* pCaller = (CallersData*) data; + if(pCaller->skip == 0) { + pCaller->pMethod = pFunc; + return SWA_ABORT; + } + else { + pCaller->skip--; + return SWA_CONTINUE; + } + +} +#endif // CROSSGEN_COMPILE + +#ifdef CROSSGEN_COMPILE +// defined in compile.cpp +extern CompilationDomain * theDomain; +#endif + +void SystemDomain::CreateDefaultDomain() +{ + STANDARD_VM_CONTRACT; + +#ifdef CROSSGEN_COMPILE + AppDomainRefHolder pDomain(theDomain); +#else + AppDomainRefHolder pDomain(new AppDomain()); +#endif + + SystemDomain::LockHolder lh; + pDomain->Init(); + + Security::SetDefaultAppDomainProperty(pDomain->GetSecurityDescriptor()); + + // need to make this assignment here since we'll be releasing + // the lock before calling AddDomain. So any other thread + // grabbing this lock after we release it will find that + // the COM Domain has already been created + m_pDefaultDomain = pDomain; + _ASSERTE (pDomain->GetId().m_dwId == DefaultADID); + + // allocate a Virtual Call Stub Manager for the default domain + m_pDefaultDomain->InitVSD(); + + pDomain->SetStage(AppDomain::STAGE_OPEN); + pDomain.SuppressRelease(); + + LOG((LF_CLASSLOADER | LF_CORDB, + LL_INFO10, + "Created default domain at %p\n", m_pDefaultDomain)); +} + +#ifdef DEBUGGING_SUPPORTED + +void SystemDomain::PublishAppDomainAndInformDebugger (AppDomain *pDomain) +{ + CONTRACTL + { + if(!g_fEEInit) {THROWS;} else {DISABLED(NOTHROW);}; + if(!g_fEEInit) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);}; + MODE_ANY; + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO100, "SD::PADAID: Adding 0x%x\n", pDomain)); + + // Call the publisher API to add this appdomain entry to the list + // The publisher will handle failures, so we don't care if this succeeds or fails. + if (g_pDebugInterface != NULL) + { + g_pDebugInterface->AddAppDomainToIPC(pDomain); + } +} + +#endif // DEBUGGING_SUPPORTED + +void SystemDomain::AddDomain(AppDomain* pDomain) +{ + CONTRACTL + { + NOTHROW; + MODE_ANY; + GC_TRIGGERS; + PRECONDITION(CheckPointer((pDomain))); + } + CONTRACTL_END; + + { + LockHolder lh; + + _ASSERTE (pDomain->m_Stage != AppDomain::STAGE_CREATING); + if (pDomain->m_Stage == AppDomain::STAGE_READYFORMANAGEDCODE || + pDomain->m_Stage == AppDomain::STAGE_ACTIVE) + { + pDomain->SetStage(AppDomain::STAGE_OPEN); + IncrementNumAppDomains(); // Maintain a count of app domains added to the list. + } + } + + // Note that if you add another path that can reach here without calling + // PublishAppDomainAndInformDebugger, then you should go back & make sure + // that PADAID gets called. Right after this call, if not sooner. + LOG((LF_CORDB, LL_INFO1000, "SD::AD:Would have added domain here! 0x%x\n", + pDomain)); +} + +BOOL SystemDomain::RemoveDomain(AppDomain* pDomain) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(CheckPointer(pDomain)); + PRECONDITION(!pDomain->IsDefaultDomain()); + } + CONTRACTL_END; + + // You can not remove the default domain. + + + if (!pDomain->IsActive()) + return FALSE; + + pDomain->Release(); + + return TRUE; +} + + +#ifdef PROFILING_SUPPORTED +void SystemDomain::NotifyProfilerStartup() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + { + BEGIN_PIN_PROFILER(CORProfilerTrackAppDomainLoads()); + _ASSERTE(System()); + g_profControlBlock.pProfInterface->AppDomainCreationStarted((AppDomainID) System()); + END_PIN_PROFILER(); + } + + { + BEGIN_PIN_PROFILER(CORProfilerTrackAppDomainLoads()); + _ASSERTE(System()); + g_profControlBlock.pProfInterface->AppDomainCreationFinished((AppDomainID) System(), S_OK); + END_PIN_PROFILER(); + } + + { + BEGIN_PIN_PROFILER(CORProfilerTrackAppDomainLoads()); + _ASSERTE(System()->DefaultDomain()); + g_profControlBlock.pProfInterface->AppDomainCreationStarted((AppDomainID) System()->DefaultDomain()); + END_PIN_PROFILER(); + } + + { + BEGIN_PIN_PROFILER(CORProfilerTrackAppDomainLoads()); + _ASSERTE(System()->DefaultDomain()); + g_profControlBlock.pProfInterface->AppDomainCreationFinished((AppDomainID) System()->DefaultDomain(), S_OK); + END_PIN_PROFILER(); + } + + { + BEGIN_PIN_PROFILER(CORProfilerTrackAppDomainLoads()); + _ASSERTE(SharedDomain::GetDomain()); + g_profControlBlock.pProfInterface->AppDomainCreationStarted((AppDomainID) SharedDomain::GetDomain()); + END_PIN_PROFILER(); + } + + { + BEGIN_PIN_PROFILER(CORProfilerTrackAppDomainLoads()); + _ASSERTE(SharedDomain::GetDomain()); + g_profControlBlock.pProfInterface->AppDomainCreationFinished((AppDomainID) SharedDomain::GetDomain(), S_OK); + END_PIN_PROFILER(); + } +} + +HRESULT SystemDomain::NotifyProfilerShutdown() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + { + BEGIN_PIN_PROFILER(CORProfilerTrackAppDomainLoads()); + _ASSERTE(System()); + g_profControlBlock.pProfInterface->AppDomainShutdownStarted((AppDomainID) System()); + END_PIN_PROFILER(); + } + + { + BEGIN_PIN_PROFILER(CORProfilerTrackAppDomainLoads()); + _ASSERTE(System()); + g_profControlBlock.pProfInterface->AppDomainShutdownFinished((AppDomainID) System(), S_OK); + END_PIN_PROFILER(); + } + + { + BEGIN_PIN_PROFILER(CORProfilerTrackAppDomainLoads()); + _ASSERTE(System()->DefaultDomain()); + g_profControlBlock.pProfInterface->AppDomainShutdownStarted((AppDomainID) System()->DefaultDomain()); + END_PIN_PROFILER(); + } + + { + BEGIN_PIN_PROFILER(CORProfilerTrackAppDomainLoads()); + _ASSERTE(System()->DefaultDomain()); + g_profControlBlock.pProfInterface->AppDomainShutdownFinished((AppDomainID) System()->DefaultDomain(), S_OK); + END_PIN_PROFILER(); + } + return (S_OK); +} +#endif // PROFILING_SUPPORTED + +#ifdef FEATURE_FUSION +static HRESULT GetVersionPath(HKEY root, __in LPWSTR key, __out LPWSTR* pDevpath, DWORD* pdwDevpath) +{ + CONTRACTL + { + MODE_PREEMPTIVE; + NOTHROW; + GC_NOTRIGGER; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END; + + DWORD rtn; + RegKeyHolder versionKey; + rtn = WszRegOpenKeyEx(root, key, 0, KEY_READ, &versionKey); + if(rtn == ERROR_SUCCESS) { + DWORD type; + DWORD cbDevpath; + if(WszRegQueryValueEx(versionKey, W("devpath"), 0, &type, (LPBYTE) NULL, &cbDevpath) == ERROR_SUCCESS && type == REG_SZ) { + *pDevpath = (LPWSTR) new (nothrow) BYTE[cbDevpath]; + if(*pDevpath == NULL) + return E_OUTOFMEMORY; + else { + rtn = WszRegQueryValueEx(versionKey, W("devpath"), 0, &type, (LPBYTE) *pDevpath, &cbDevpath); + if ((rtn == ERROR_SUCCESS) && (type == REG_SZ)) + *pdwDevpath = (DWORD) wcslen(*pDevpath); + } + } + else + return REGDB_E_INVALIDVALUE; + } + + return HRESULT_FROM_WIN32(rtn); +} + +// Get the developers path from the environment. This can only be set through the environment and +// cannot be added through configuration files, registry etc. This would make it to easy for +// developers to deploy apps that are not side by side. The environment variable should only +// be used on developers machines where exact matching to versions makes build and testing to +// difficult. +void SystemDomain::GetDevpathW(__out_ecount_opt(1) LPWSTR* pDevpath, DWORD* pdwDevpath) +{ + CONTRACTL + { + THROWS; + MODE_ANY; + GC_TRIGGERS; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + GCX_PREEMP(); + + if(g_pConfig->DeveloperInstallation() && m_fDevpath == FALSE) { + + LockHolder lh; + + if(m_fDevpath == FALSE) { + DWORD dwPath = 0; + PathString m_pwDevpathholder; + dwPath = WszGetEnvironmentVariable(APPENV_DEVPATH, m_pwDevpathholder); + if(dwPath) { + m_pwDevpath = m_pwDevpathholder.GetCopyOfUnicodeString(); + } + else { + RegKeyHolder userKey; + RegKeyHolder machineKey; + + WCHAR pVersion[MAX_PATH_FNAME]; + DWORD dwVersion = MAX_PATH_FNAME; + HRESULT hr = S_OK; + hr = FusionBind::GetVersion(pVersion, &dwVersion); + if(SUCCEEDED(hr)) { + LONG rslt; + rslt = WszRegOpenKeyEx(HKEY_CURRENT_USER, FRAMEWORK_REGISTRY_KEY_W,0,KEY_READ, &userKey); + hr = HRESULT_FROM_WIN32(rslt); + if (SUCCEEDED(hr)) { + hr = GetVersionPath(userKey, pVersion, &m_pwDevpath, &m_dwDevpath); + } + + if (FAILED(hr) && WszRegOpenKeyEx(HKEY_LOCAL_MACHINE, FRAMEWORK_REGISTRY_KEY_W,0,KEY_READ, &machineKey) == ERROR_SUCCESS) { + hr = GetVersionPath(machineKey, pVersion, &m_pwDevpath, &m_dwDevpath); + } + } + if (Assembly::FileNotFound(hr)) + hr = S_FALSE; + else + IfFailThrow(hr); + } + + m_fDevpath = TRUE; + } + // lh out of scope here + } + + if(pDevpath) *pDevpath = m_pwDevpath; + if(pdwDevpath) *pdwDevpath = m_dwDevpath; + return; +} +#endif // FEATURE_FUSION + +#ifdef _DEBUG +struct AppDomain::ThreadTrackInfo { + Thread *pThread; + CDynArray frameStack; +}; +#endif // _DEBUG + +AppDomain::AppDomain() +{ + // initialize fields so the appdomain can be safely destructed + // shouldn't call anything that can fail here - use ::Init instead + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + FORBID_FAULT; + } + CONTRACTL_END; + + m_cRef=1; + m_pNextInDelayedUnloadList = NULL; + m_pSecContext = NULL; + m_fRudeUnload = FALSE; + m_pUnloadRequestThread = NULL; + m_ADUnloadSink=NULL; + +#ifndef FEATURE_CORECLR + m_bUseOsSorting = RunningOnWin8(); + m_sortVersion = DEFAULT_SORT_VERSION; + m_pCustomSortLibrary = NULL; +#if _DEBUG + m_bSortingInitialized = FALSE; +#endif // _DEBUG + m_pNlsHashProvider = NULL; +#endif //!FEATURE_CORECLR + + // Initialize Shared state. Assemblies are loaded + // into each domain by default. +#ifdef FEATURE_LOADER_OPTIMIZATION + m_SharePolicy = SHARE_POLICY_UNSPECIFIED; +#endif + + m_pRootAssembly = NULL; + + m_pwDynamicDir = NULL; + + m_dwFlags = 0; + m_pSecDesc = NULL; + m_pDefaultContext = NULL; +#ifdef FEATURE_COMINTEROP + m_pComCallWrapperCache = NULL; + m_pRCWCache = NULL; + m_pRCWRefCache = NULL; + m_pLicenseInteropHelperMT = NULL; + m_COMorRemotingFlag = COMorRemoting_NotInitialized; + memset(m_rpCLRTypes, 0, sizeof(m_rpCLRTypes)); +#endif // FEATURE_COMINTEROP + + m_pUMEntryThunkCache = NULL; + + m_pAsyncPool = NULL; + m_hHandleTableBucket = NULL; + + m_ExposedObject = NULL; + m_pComIPForExposedObject = NULL; + + #ifdef _DEBUG + m_pThreadTrackInfoList = NULL; + m_TrackSpinLock = 0; + m_Assemblies.Debug_SetAppDomain(this); +#endif // _DEBUG + + m_dwThreadEnterCount = 0; + m_dwThreadsStillInAppDomain = (ULONG)-1; + + m_pSecDesc = NULL; + m_hHandleTableBucket=NULL; + + m_ExposedObject = NULL; + +#ifdef FEATURE_COMINTEROP + m_pRefDispIDCache = NULL; + m_hndMissing = NULL; +#endif + + m_pRefClassFactHash = NULL; + m_anonymouslyHostedDynamicMethodsAssembly = NULL; + + m_ReversePInvokeCanEnter=TRUE; + m_ForceTrivialWaitOperations = false; + m_Stage=STAGE_CREATING; + + m_bForceGCOnUnload=FALSE; + m_bUnloadingFromUnloadEvent=FALSE; +#ifdef _DEBUG + m_dwIterHolders=0; + m_dwRefTakers=0; + m_dwCreationHolders=0; +#endif + +#ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING + m_ullTotalProcessorUsage = 0; + m_pullAllocBytes = NULL; + m_pullSurvivedBytes = NULL; +#endif //FEATURE_APPDOMAIN_RESOURCE_MONITORING + +#ifdef FEATURE_TYPEEQUIVALENCE + m_pTypeEquivalenceTable = NULL; +#endif // FEATURE_TYPEEQUIVALENCE + +#ifdef FEATURE_COMINTEROP +#ifdef FEATURE_REFLECTION_ONLY_LOAD + m_pReflectionOnlyWinRtBinder = NULL; + m_pReflectionOnlyWinRtTypeCache = NULL; +#endif // FEATURE_REFLECTION_ONLY_LOAD + m_pNameToTypeMap = NULL; + m_vNameToTypeMapVersion = 0; + m_nEpoch = 0; + m_pWinRTFactoryCache = NULL; +#endif // FEATURE_COMINTEROP + + m_fAppDomainManagerSetInConfig = FALSE; + m_dwAppDomainManagerInitializeDomainFlags = eInitializeNewDomainFlags_None; + +#ifdef FEATURE_PREJIT + m_pDomainFileWithNativeImageList = NULL; +#endif + +#if defined(FEATURE_HOST_ASSEMBLY_RESOLVER) + m_fIsBindingModelLocked.Store(FALSE); +#endif // defined(FEATURE_HOST_ASSEMBLY_RESOLVER) + +} // AppDomain::AppDomain + +AppDomain::~AppDomain() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + +#ifndef CROSSGEN_COMPILE + + _ASSERTE(m_dwCreationHolders == 0); + + // release the TPIndex. note that since TPIndex values are recycled the TPIndex + // can only be released once all threads in the AppDomain have exited. + if (GetTPIndex().m_dwIndex != 0) + PerAppDomainTPCountList::ResetAppDomainIndex(GetTPIndex()); + + if (m_dwId.m_dwId!=0) + SystemDomain::ReleaseAppDomainId(m_dwId); + + m_AssemblyCache.Clear(); + + if (m_ADUnloadSink) + m_ADUnloadSink->Release(); + + if (m_pSecContext) + delete m_pSecContext; + + if(!g_fEEInit) + Terminate(); + +#ifndef FEATURE_CORECLR + if (m_pCustomSortLibrary) + delete m_pCustomSortLibrary; + + if (m_pNlsHashProvider) + delete m_pNlsHashProvider; +#endif + + +#ifdef FEATURE_REMOTING + if (!g_fEEInit) + { + GCX_COOP(); // See SystemDomain::EnumAllStaticGCRefs if you are removing this + CrossDomainTypeMap::FlushStaleEntries(); + CrossDomainFieldMap::FlushStaleEntries(); + } +#endif // FEATURE_REMOTING + +#ifdef FEATURE_COMINTEROP +#ifdef FEATURE_REFLECTION_ONLY_LOAD + if (m_pReflectionOnlyWinRtBinder != NULL) + { + m_pReflectionOnlyWinRtBinder->Release(); + } + if (m_pReflectionOnlyWinRtTypeCache != NULL) + { + m_pReflectionOnlyWinRtTypeCache->Release(); + } +#endif // FEATURE_REFLECTION_ONLY_LOAD + if (m_pNameToTypeMap != nullptr) + { + delete m_pNameToTypeMap; + m_pNameToTypeMap = nullptr; + } + if (m_pWinRTFactoryCache != nullptr) + { + delete m_pWinRTFactoryCache; + m_pWinRTFactoryCache = nullptr; + } +#endif //FEATURE_COMINTEROP + +#ifdef _DEBUG + // If we were tracking thread AD transitions, cleanup the list on shutdown + if (m_pThreadTrackInfoList) + { + while (m_pThreadTrackInfoList->Count() > 0) + { + // Get the very last element + ThreadTrackInfo *pElem = *(m_pThreadTrackInfoList->Get(m_pThreadTrackInfoList->Count() - 1)); + _ASSERTE(pElem); + + // Free the memory + delete pElem; + + // Remove pointer entry from the list + m_pThreadTrackInfoList->Delete(m_pThreadTrackInfoList->Count() - 1); + } + + // Now delete the list itself + delete m_pThreadTrackInfoList; + m_pThreadTrackInfoList = NULL; + } +#endif // _DEBUG + +#endif // CROSSGEN_COMPILE +} + +//***************************************************************************** +//***************************************************************************** +//***************************************************************************** +#ifdef _DEBUG +#include "handletablepriv.h" +#endif + + + +void AppDomain::Init() +{ + CONTRACTL + { + STANDARD_VM_CHECK; + PRECONDITION(SystemDomain::IsUnderDomainLock()); + } + CONTRACTL_END; + + m_pDelayedLoaderAllocatorUnloadList = NULL; + + SetStage( STAGE_CREATING); + + + // The lock is taken also during stack walking (GC or profiler) + // - To prevent deadlock with GC thread, we cannot trigger GC while holding the lock + // - To prevent deadlock with profiler thread, we cannot allow thread suspension + m_crstHostAssemblyMap.Init( + CrstHostAssemblyMap, + (CrstFlags)(CRST_GC_NOTRIGGER_WHEN_TAKEN + | CRST_DEBUGGER_THREAD + INDEBUG(| CRST_DEBUG_ONLY_CHECK_FORBID_SUSPEND_THREAD))); + m_crstHostAssemblyMapAdd.Init(CrstHostAssemblyMapAdd); + + m_dwId = SystemDomain::GetNewAppDomainId(this); + + m_LoaderAllocator.Init(this); + +#ifndef CROSSGEN_COMPILE + //Allocate the threadpool entry before the appdomin id list. Otherwise, + //the thread pool list will be out of sync if insertion of id in + //the appdomain fails. + m_tpIndex = PerAppDomainTPCountList::AddNewTPIndex(); +#endif // CROSSGEN_COMPILE + + m_dwIndex = SystemDomain::GetNewAppDomainIndex(this); + +#ifndef CROSSGEN_COMPILE + PerAppDomainTPCountList::SetAppDomainId(m_tpIndex, m_dwId); + + m_ADUnloadSink=new ADUnloadSink(); +#endif + + BaseDomain::Init(); + + // Set up the IL stub cache + m_ILStubCache.Init(GetLoaderAllocator()->GetHighFrequencyHeap()); + + m_pSecContext = new SecurityContext (GetLowFrequencyHeap()); + +// Set up the binding caches + m_AssemblyCache.Init(&m_DomainCacheCrst, GetHighFrequencyHeap()); + m_UnmanagedCache.InitializeTable(this, &m_DomainCacheCrst); + + m_MemoryPressure = 0; + + m_sDomainLocalBlock.Init(this); + +#ifndef CROSSGEN_COMPILE + +#ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING + // NOTE: it's important that we initialize ARM data structures before calling + // Ref_CreateHandleTableBucket, this is because AD::Init() can race with GC + // and once we add ourselves to the handle table map the GC can start walking + // our handles and calling AD::RecordSurvivedBytes() which touches ARM data. + if (GCHeap::IsServerHeap()) + m_dwNumHeaps = CPUGroupInfo::CanEnableGCCPUGroups() ? + CPUGroupInfo::GetNumActiveProcessors() : + GetCurrentProcessCpuCount(); + else + m_dwNumHeaps = 1; + m_pullAllocBytes = new ULONGLONG [m_dwNumHeaps * ARM_CACHE_LINE_SIZE_ULL]; + m_pullSurvivedBytes = new ULONGLONG [m_dwNumHeaps * ARM_CACHE_LINE_SIZE_ULL]; + for (DWORD i = 0; i < m_dwNumHeaps; i++) + { + m_pullAllocBytes[i * ARM_CACHE_LINE_SIZE_ULL] = 0; + m_pullSurvivedBytes[i * ARM_CACHE_LINE_SIZE_ULL] = 0; + } + m_ullLastEtwAllocBytes = 0; +#endif //FEATURE_APPDOMAIN_RESOURCE_MONITORING + + // Default domain reuses the handletablemap that was created during EEStartup since + // default domain cannot be unloaded. + if (GetId().m_dwId == DefaultADID) + { + m_hHandleTableBucket = g_HandleTableMap.pBuckets[0]; + } + else + { + m_hHandleTableBucket = Ref_CreateHandleTableBucket(m_dwIndex); + } + +#ifdef _DEBUG + if (((HandleTable *)(m_hHandleTableBucket->pTable[0]))->uADIndex != m_dwIndex) + _ASSERTE (!"AD index mismatch"); +#endif // _DEBUG + +#endif // CROSSGEN_COMPILE + +#ifdef FEATURE_TYPEEQUIVALENCE + m_TypeEquivalenceCrst.Init(CrstTypeEquivalenceMap); +#endif + + m_ReflectionCrst.Init(CrstReflection, CRST_UNSAFE_ANYMODE); + m_RefClassFactCrst.Init(CrstClassFactInfoHash); + + { + LockOwner lock = {&m_DomainCrst, IsOwnerOfCrst}; + m_clsidHash.Init(0,&CompareCLSID,true, &lock); // init hash table + } + + CreateSecurityDescriptor(); + SetStage(STAGE_READYFORMANAGEDCODE); + +#ifndef CROSSGEN_COMPILE + m_pDefaultContext = new Context(this); + + m_ExposedObject = CreateHandle(NULL); + + // Create the Application Security Descriptor + + COUNTER_ONLY(GetPerfCounters().m_Loading.cAppDomains++); + +#ifdef FEATURE_COMINTEROP + if (!AppX::IsAppXProcess()) + { +#ifdef FEATURE_REFLECTION_ONLY_LOAD + m_pReflectionOnlyWinRtTypeCache = clr::SafeAddRef(new CLRPrivTypeCacheReflectionOnlyWinRT()); + m_pReflectionOnlyWinRtBinder = clr::SafeAddRef(new CLRPrivBinderReflectionOnlyWinRT(m_pReflectionOnlyWinRtTypeCache)); +#endif + } +#ifdef FEATURE_APPX_BINDER + else if (g_fEEStarted && !IsDefaultDomain()) + { // Non-default domain in an AppX process. This exists only for designers and we'd better be in dev mode. + _ASSERTE(IsCompilationProcess() || AppX::IsAppXDesignMode()); + + // Inherit AppX binder from default domain. + SetLoadContextHostBinder(SystemDomain::System()->DefaultDomain()->GetLoadContextHostBinder()); + + // Note: LoadFrom, LoadFile, Load(byte[], ...), ReflectionOnlyLoad, LoadWithPartialName, + /// etc. are not supported and are actively blocked. + } +#endif //FEATURE_APPX_BINDER +#endif //FEATURE_COMINTEROP + +#endif // CROSSGEN_COMPILE +} // AppDomain::Init + + +/*********************************************************************/ + +BOOL AppDomain::IsCompilationDomain() +{ + LIMITED_METHOD_CONTRACT; + + BOOL isCompilationDomain = (m_dwFlags & COMPILATION_DOMAIN) != 0; +#ifdef FEATURE_PREJIT + _ASSERTE(!isCompilationDomain || + (IsCompilationProcess() && IsPassiveDomain())); +#endif // FEATURE_PREJIT + return isCompilationDomain; +} + +#ifndef CROSSGEN_COMPILE + +extern int g_fADUnloadWorkerOK; + +// Notes: +// This helper will send the AppDomain creation notifications for profiler / debugger. +// If it throws, its backout code will also send a notification. +// If it succeeds, then we still need to send a AppDomainCreateFinished notification. +void AppDomain::CreateUnmanagedObject(AppDomainCreationHolder& pDomain) +{ + CONTRACTL + { + THROWS; + MODE_COOPERATIVE; + GC_TRIGGERS; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + GCX_PREEMP(); + + pDomain.Assign(new AppDomain()); + if (g_fADUnloadWorkerOK<0) + { + AppDomain::CreateADUnloadWorker(); + } + + //@todo: B#25921 + // We addref Appdomain object here and notify a profiler that appdomain + // creation has started, then return to managed code which will call + // the function that releases the appdomain and notifies a profiler that we finished + // creating the appdomain. If an exception is raised while we're in that managed code + // we will leak memory and the profiler will not be notified about the failure + +#ifdef PROFILING_SUPPORTED + // Signal profile if present. + { + BEGIN_PIN_PROFILER(CORProfilerTrackAppDomainLoads()); + g_profControlBlock.pProfInterface->AppDomainCreationStarted((AppDomainID) (AppDomain*) pDomain); + END_PIN_PROFILER(); + } + EX_TRY +#endif // PROFILING_SUPPORTED + { + { + SystemDomain::LockHolder lh; + pDomain->Init(); + // allocate a Virtual Call Stub Manager for this domain + pDomain->InitVSD(); + } + + pDomain->SetCanUnload(); // by default can unload any domain + + #ifdef DEBUGGING_SUPPORTED + // Notify the debugger here, before the thread transitions into the + // AD to finish the setup, and before any assemblies are loaded into it. + SystemDomain::PublishAppDomainAndInformDebugger(pDomain); + #endif // DEBUGGING_SUPPORTED + + STRESS_LOG2 (LF_APPDOMAIN, LL_INFO100, "Create domain [%d] %p\n", pDomain->GetId().m_dwId, (AppDomain*)pDomain); + pDomain->LoadSystemAssemblies(); + pDomain->SetupSharedStatics(); + + pDomain->SetStage(AppDomain::STAGE_ACTIVE); + } +#ifdef PROFILING_SUPPORTED + EX_HOOK + { + // Need the first assembly loaded in to get any data on an app domain. + { + BEGIN_PIN_PROFILER(CORProfilerTrackAppDomainLoads()); + g_profControlBlock.pProfInterface->AppDomainCreationFinished((AppDomainID)(AppDomain*) pDomain, GET_EXCEPTION()->GetHR()); + END_PIN_PROFILER(); + } + } + EX_END_HOOK; + + // On success, caller must still send the AppDomainCreationFinished notification. +#endif // PROFILING_SUPPORTED +} + +void AppDomain::Stop() +{ + CONTRACTL + { + NOTHROW; + MODE_ANY; + GC_TRIGGERS; + } + CONTRACTL_END; + +#ifdef FEATURE_MULTICOREJIT + GetMulticoreJitManager().StopProfile(true); +#endif + + // Set the unloaded flag before notifying the debugger + GetLoaderAllocator()->SetIsUnloaded(); + +#ifdef DEBUGGING_SUPPORTED + if (IsDebuggerAttached()) + NotifyDebuggerUnload(); +#endif // DEBUGGING_SUPPORTED + + m_pRootAssembly = NULL; // This assembly is in the assembly list; + + if (m_pSecDesc != NULL) + { + delete m_pSecDesc; + m_pSecDesc = NULL; + } + +#ifdef DEBUGGING_SUPPORTED + if (NULL != g_pDebugInterface) + { + // Call the publisher API to delete this appdomain entry from the list + CONTRACT_VIOLATION(ThrowsViolation); + g_pDebugInterface->RemoveAppDomainFromIPC (this); + } +#endif // DEBUGGING_SUPPORTED +} + +void AppDomain::Terminate() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + GCX_PREEMP(); + + + _ASSERTE(m_dwThreadEnterCount == 0 || IsDefaultDomain()); + + if (m_pComIPForExposedObject) + { + m_pComIPForExposedObject->Release(); + m_pComIPForExposedObject = NULL; + } + + delete m_pDefaultContext; + m_pDefaultContext = NULL; + + if (m_pUMEntryThunkCache) + { + delete m_pUMEntryThunkCache; + m_pUMEntryThunkCache = NULL; + } + +#ifdef FEATURE_COMINTEROP + if (m_pRCWCache) + { + delete m_pRCWCache; + m_pRCWCache = NULL; + } + + if (m_pRCWRefCache) + { + delete m_pRCWRefCache; + m_pRCWRefCache = NULL; + } + + if (m_pComCallWrapperCache) + { + m_pComCallWrapperCache->Neuter(); + m_pComCallWrapperCache->Release(); + } + + // if the above released the wrapper cache, then it will call back and reset our + // m_pComCallWrapperCache to null. If not null, then need to set it's domain pointer to + // null. + if (! m_pComCallWrapperCache) + { + LOG((LF_APPDOMAIN, LL_INFO10, "AppDomain::Terminate ComCallWrapperCache released\n")); + } +#ifdef _DEBUG + else + { + m_pComCallWrapperCache = NULL; + LOG((LF_APPDOMAIN, LL_INFO10, "AppDomain::Terminate ComCallWrapperCache not released\n")); + } +#endif // _DEBUG + +#endif // FEATURE_COMINTEROP + +#ifdef FEATURE_FUSION + if(m_pAsyncPool != NULL) + { + delete m_pAsyncPool; + m_pAsyncPool = NULL; + } +#endif + + if (!IsAtProcessExit()) + { + // if we're not shutting down everything then clean up the string literals associated + // with this appdomain -- note that is no longer needs to happen while suspended + // because the appropriate locks are taken in the GlobalStringLiteralMap + // this is important as this locks have a higher lock number than does the + // thread-store lock which is taken when we suspend. + GetLoaderAllocator()->CleanupStringLiteralMap(); + + // Suspend the EE to do some clean up that can only occur + // while no threads are running. + GCX_COOP (); // SuspendEE may require current thread to be in Coop mode + ThreadSuspend::SuspendEE(ThreadSuspend::SUSPEND_FOR_APPDOMAIN_SHUTDOWN); + } + + // Note that this must be performed before restarting the EE. It will clean + // the cache and prevent others from using stale cache entries. + //@TODO: Would be nice to get this back to BaseDomain, but need larger fix for that. + // NOTE: Must have the runtime suspended to unlink managers + // NOTE: May be NULL due to OOM during initialization. Can skip in that case. + GetLoaderAllocator()->UninitVirtualCallStubManager(); + MethodTable::ClearMethodDataCache(); + ClearJitGenericHandleCache(this); + + // @TODO s_TPMethodTableCrst prevents us from from keeping the whole + // assembly shutdown logic here. See if we can do better in the next milestone +#ifdef FEATURE_PREJIT + DeleteNativeCodeRanges(); +#endif + + if (!IsAtProcessExit()) + { + // Resume the EE. + ThreadSuspend::RestartEE(FALSE, TRUE); + } + + ShutdownAssemblies(); +#ifdef FEATURE_CORECLR + ShutdownNativeDllSearchDirectories(); +#endif + + if (m_pRefClassFactHash) + { + m_pRefClassFactHash->Destroy(); + // storage for m_pRefClassFactHash itself is allocated on the loader heap + } + +#ifdef FEATURE_TYPEEQUIVALENCE + m_TypeEquivalenceCrst.Destroy(); +#endif + + m_ReflectionCrst.Destroy(); + m_RefClassFactCrst.Destroy(); + + m_LoaderAllocator.Terminate(); + + BaseDomain::Terminate(); + +#ifdef _DEBUG + if (m_hHandleTableBucket && + m_hHandleTableBucket->pTable && + ((HandleTable *)(m_hHandleTableBucket->pTable[0]))->uADIndex != m_dwIndex) + _ASSERTE (!"AD index mismatch"); +#endif // _DEBUG + + if (m_hHandleTableBucket) { + Ref_DestroyHandleTableBucket(m_hHandleTableBucket); + m_hHandleTableBucket = NULL; + } + +#ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING + if (m_pullAllocBytes) + { + delete [] m_pullAllocBytes; + } + if (m_pullSurvivedBytes) + { + delete [] m_pullSurvivedBytes; + } +#endif //FEATURE_APPDOMAIN_RESOURCE_MONITORING + + if(m_dwIndex.m_dwIndex != 0) + SystemDomain::ReleaseAppDomainIndex(m_dwIndex); +} // AppDomain::Terminate + +void AppDomain::CloseDomain() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + + BOOL bADRemoved=FALSE;; + + AddRef(); // Hold a reference + AppDomainRefHolder AdHolder(this); + { + SystemDomain::LockHolder lh; + + SystemDomain::System()->DecrementNumAppDomains(); // Maintain a count of app domains added to the list. + bADRemoved = SystemDomain::System()->RemoveDomain(this); + } + + if(bADRemoved) + Stop(); +} + +/*********************************************************************/ + +struct GetExposedObject_Args +{ + AppDomain *pDomain; + OBJECTREF *ref; +}; + +static void GetExposedObject_Wrapper(LPVOID ptr) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + GetExposedObject_Args *args = (GetExposedObject_Args *) ptr; + *(args->ref) = args->pDomain->GetExposedObject(); +} + + +OBJECTREF AppDomain::GetExposedObject() +{ + CONTRACTL + { + MODE_COOPERATIVE; + THROWS; + GC_TRIGGERS; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + OBJECTREF ref = GetRawExposedObject(); + if (ref == NULL) + { + APPDOMAINREF obj = NULL; + + Thread *pThread = GetThread(); + if (pThread->GetDomain() != this) + { + GCPROTECT_BEGIN(ref); + GetExposedObject_Args args = {this, &ref}; + // call through DoCallBack with a domain transition + pThread->DoADCallBack(this,GetExposedObject_Wrapper, &args,ADV_CREATING|ADV_RUNNINGIN); + GCPROTECT_END(); + return ref; + } + MethodTable *pMT = MscorlibBinder::GetClass(CLASS__APP_DOMAIN); + + // Create the module object + obj = (APPDOMAINREF) AllocateObject(pMT); + obj->SetDomain(this); + + if(StoreFirstObjectInHandle(m_ExposedObject, (OBJECTREF) obj) == FALSE) { + obj = (APPDOMAINREF) GetRawExposedObject(); + _ASSERTE(obj); + } + + return (OBJECTREF) obj; + } + + return ref; +} + +#ifndef FEATURE_CORECLR +void AppDomain::InitializeSorting(OBJECTREF* ppAppdomainSetup) +{ + CONTRACTL + { + MODE_COOPERATIVE; + THROWS; + GC_NOTRIGGER; + PRECONDITION(ppAppdomainSetup == NULL || IsProtectedByGCFrame(ppAppdomainSetup)); + } + CONTRACTL_END; + + DWORD sortVersionFromConfig = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_CompatSortNLSVersion); + + if(sortVersionFromConfig != 0) + { + m_bUseOsSorting = FALSE; + m_sortVersion = sortVersionFromConfig; + } + + if(ppAppdomainSetup != NULL) + { + APPDOMAINSETUPREF adSetup = (APPDOMAINSETUPREF) *ppAppdomainSetup; + APPDOMAINSORTINGSETUPINFOREF sortingSetup = adSetup->GetAppDomainSortingSetupInfo(); + + if(sortingSetup != NULL) + { + if(sortingSetup->UseV2LegacySorting() || sortingSetup->UseV4LegacySorting()) + { + + m_bUseOsSorting = FALSE; + + if(sortingSetup->UseV2LegacySorting()) + { + m_sortVersion = SORT_VERSION_WHIDBEY; + } + + if(sortingSetup->UseV4LegacySorting()) + { + m_sortVersion = SORT_VERSION_V4; + } + } + else if(sortingSetup->GetPFNIsNLSDefinedString() != NULL + && sortingSetup->GetPFNCompareStringEx() != NULL + && sortingSetup->GetPFNLCMapStringEx() != NULL + && sortingSetup->GetPFNFindNLSStringEx() != NULL + && sortingSetup->GetPFNCompareStringOrdinal() != NULL + && sortingSetup->GetPFNGetNLSVersionEx() != NULL + && sortingSetup->GetPFNFindStringOrdinal() != NULL) + { + m_pCustomSortLibrary = new COMNlsCustomSortLibrary; + m_pCustomSortLibrary->pIsNLSDefinedString = (PFN_IS_NLS_DEFINED_STRING) sortingSetup->GetPFNIsNLSDefinedString(); + m_pCustomSortLibrary->pCompareStringEx = (PFN_COMPARE_STRING_EX) sortingSetup->GetPFNCompareStringEx(); + m_pCustomSortLibrary->pLCMapStringEx = (PFN_LC_MAP_STRING_EX) sortingSetup->GetPFNLCMapStringEx(); + m_pCustomSortLibrary->pFindNLSStringEx = (PFN_FIND_NLS_STRING_EX) sortingSetup->GetPFNFindNLSStringEx(); + m_pCustomSortLibrary->pCompareStringOrdinal = (PFN_COMPARE_STRING_ORDINAL) sortingSetup->GetPFNCompareStringOrdinal(); + m_pCustomSortLibrary->pGetNLSVersionEx = (PFN_GET_NLS_VERSION_EX) sortingSetup->GetPFNGetNLSVersionEx(); + m_pCustomSortLibrary->pFindStringOrdinal = (PFN_FIND_STRING_ORDINAL) sortingSetup->GetPFNFindStringOrdinal(); + } + } + } + + if(m_bUseOsSorting == FALSE && m_sortVersion == DEFAULT_SORT_VERSION) + { + // If we are using the legacy sorting dlls, the default version for sorting is SORT_VERSION_V4. Note that + // we don't expect this to change in the future (even when V5 or V6 of the runtime comes out). + m_sortVersion = SORT_VERSION_V4; + } + + if(RunningOnWin8() && m_bUseOsSorting == FALSE) + { + // We need to ensure that the versioned sort DLL could load so we don't crash later. This ensures we have + // the same behavior as Windows 7, where even if we couldn't load the correct versioned sort dll, we would + // provide the default sorting behavior. + INT_PTR sortOrigin; + if(COMNlsInfo::InternalInitVersionedSortHandle(W(""), &sortOrigin, m_sortVersion) == NULL) + { + LOG((LF_APPDOMAIN, LL_WARNING, "AppDomain::InitializeSorting failed to load legacy sort DLL for AppDomain.\n")); + // We couldn't load a sort DLL. Fall back to default sorting using the OS. + m_bUseOsSorting = TRUE; + m_sortVersion = DEFAULT_SORT_VERSION; + } + } + +#if _DEBUG + m_bSortingInitialized = TRUE; +#endif +} +#endif + +#ifndef FEATURE_CORECLR +void AppDomain::InitializeHashing(OBJECTREF* ppAppdomainSetup) +{ + CONTRACTL + { + MODE_COOPERATIVE; + THROWS; + GC_NOTRIGGER; + PRECONDITION(ppAppdomainSetup == NULL || IsProtectedByGCFrame(ppAppdomainSetup)); + } + CONTRACTL_END; + + m_pNlsHashProvider = new COMNlsHashProvider; + +#ifdef FEATURE_RANDOMIZED_STRING_HASHING + BOOL fUseRandomizedHashing = (BOOL) CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_UseRandomizedStringHashAlgorithm); + + if(ppAppdomainSetup != NULL) + { + APPDOMAINSETUPREF adSetup = (APPDOMAINSETUPREF) *ppAppdomainSetup; + fUseRandomizedHashing |= adSetup->UseRandomizedStringHashing(); + } + + m_pNlsHashProvider->SetUseRandomHashing(fUseRandomizedHashing); +#endif // FEATURE_RANDOMIZED_STRING_HASHING +} +#endif // FEATURE_CORECLR + +OBJECTREF AppDomain::DoSetup(OBJECTREF* setupInfo) +{ + CONTRACTL + { + MODE_COOPERATIVE; + THROWS; + GC_TRIGGERS; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + ADID adid=GetAppDomain()->GetId(); + + OBJECTREF retval=NULL; + GCPROTECT_BEGIN(retval); + + ENTER_DOMAIN_PTR(this,ADV_CREATING); + + MethodDescCallSite setup(METHOD__APP_DOMAIN__SETUP); + + ARG_SLOT args[1]; + + args[0]=ObjToArgSlot(*setupInfo); + + OBJECTREF activator; + activator=setup.Call_RetOBJECTREF(args); +#ifdef FEATURE_REMOTING + if (activator != NULL) + { + GCPROTECT_BEGIN(activator); + retval=AppDomainHelper::CrossContextCopyTo(adid,&activator); + GCPROTECT_END(); + } +#else + _ASSERTE(activator==NULL); +#endif + +#if defined(FEATURE_MULTICOREJIT) + // Disable AutoStartProfile in default domain from this code path. + // It's called from SystemDomain::ExecuteMainMethod for normal program, not needed for SL and Asp.Net + if (! IsDefaultDomain()) + { + GCX_PREEMP(); + + GetMulticoreJitManager().AutoStartProfile(this); + } +#endif + + END_DOMAIN_TRANSITION; + GCPROTECT_END(); + return retval; +} + +#endif // !CROSSGEN_COMPILE + +#ifdef FEATURE_COMINTEROP +#ifndef CROSSGEN_COMPILE +HRESULT AppDomain::GetComIPForExposedObject(IUnknown **pComIP) +{ + // Assumption: This function is called for AppDomain's that the current + // thread is in or has entered, or the AppDomain is kept alive. + // + // Assumption: This function can now throw. The caller is responsible for any + // BEGIN_EXTERNAL_ENTRYPOINT, EX_TRY, or other + // techniques to convert to a COM HRESULT protocol. + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + Thread *pThread = GetThread(); + if (m_pComIPForExposedObject) + { + GCX_PREEMP_THREAD_EXISTS(pThread); + m_pComIPForExposedObject->AddRef(); + *pComIP = m_pComIPForExposedObject; + return S_OK; + } + + IUnknown* punk = NULL; + + OBJECTREF ref = NULL; + GCPROTECT_BEGIN(ref); + + EnsureComStarted(); + + ENTER_DOMAIN_PTR(this,ADV_DEFAULTAD) + { + ref = GetExposedObject(); + punk = GetComIPFromObjectRef(&ref); + if (FastInterlockCompareExchangePointer(&m_pComIPForExposedObject, punk, NULL) == NULL) + { + GCX_PREEMP_THREAD_EXISTS(pThread); + m_pComIPForExposedObject->AddRef(); + } + } + END_DOMAIN_TRANSITION; + + GCPROTECT_END(); + + if(SUCCEEDED(hr)) + { + *pComIP = m_pComIPForExposedObject; + } + + return hr; +} +#endif //#ifndef CROSSGEN_COMPILE + +MethodTable *AppDomain::GetRedirectedType(WinMDAdapter::RedirectedTypeIndex index) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + // If we have the type loaded already, use that + if (m_rpCLRTypes[index] != nullptr) + { + return m_rpCLRTypes[index]; + } + + WinMDAdapter::FrameworkAssemblyIndex frameworkAssemblyIndex; + WinMDAdapter::GetRedirectedTypeInfo(index, nullptr, nullptr, nullptr, &frameworkAssemblyIndex, nullptr, nullptr); + MethodTable * pMT = LoadRedirectedType(index, frameworkAssemblyIndex); + m_rpCLRTypes[index] = pMT; + return pMT; +} + +MethodTable* AppDomain::LoadRedirectedType(WinMDAdapter::RedirectedTypeIndex index, WinMDAdapter::FrameworkAssemblyIndex assembly) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(index < WinMDAdapter::RedirectedTypeIndex_Count); + } + CONTRACTL_END; + + LPCSTR szClrNamespace; + LPCSTR szClrName; + LPCSTR szFullWinRTName; + WinMDAdapter::FrameworkAssemblyIndex nFrameworkAssemblyIndex; + + WinMDAdapter::GetRedirectedTypeInfo(index, &szClrNamespace, &szClrName, &szFullWinRTName, &nFrameworkAssemblyIndex, nullptr, nullptr); + + _ASSERTE(nFrameworkAssemblyIndex >= WinMDAdapter::FrameworkAssembly_Mscorlib && + nFrameworkAssemblyIndex < WinMDAdapter::FrameworkAssembly_Count); + + if (assembly != nFrameworkAssemblyIndex) + { + // The framework type does not live in the assembly we were requested to load redirected types from + return nullptr; + } + else if (nFrameworkAssemblyIndex == WinMDAdapter::FrameworkAssembly_Mscorlib) + { + return ClassLoader::LoadTypeByNameThrowing(MscorlibBinder::GetModule()->GetAssembly(), + szClrNamespace, + szClrName, + ClassLoader::ThrowIfNotFound, + ClassLoader::LoadTypes, + CLASS_LOAD_EXACTPARENTS).GetMethodTable(); + } + else + { + LPCSTR pSimpleName; + AssemblyMetaDataInternal context; + const BYTE * pbKeyToken; + DWORD cbKeyTokenLength; + DWORD dwFlags; + + WinMDAdapter::GetExtraAssemblyRefProps(nFrameworkAssemblyIndex, + &pSimpleName, + &context, + &pbKeyToken, + &cbKeyTokenLength, + &dwFlags); + + Assembly* pAssembly = AssemblySpec::LoadAssembly(pSimpleName, + &context, + pbKeyToken, + cbKeyTokenLength, + dwFlags); + + return ClassLoader::LoadTypeByNameThrowing( + pAssembly, + szClrNamespace, + szClrName, + ClassLoader::ThrowIfNotFound, + ClassLoader::LoadTypes, + CLASS_LOAD_EXACTPARENTS).GetMethodTable(); + } +} +#endif //FEATURE_COMINTEROP + +#endif //!DACCESS_COMPILE + +#ifndef DACCESS_COMPILE + +void AppDomain::CreateSecurityDescriptor() +{ + STANDARD_VM_CONTRACT; + + _ASSERTE(m_pSecDesc == NULL); + + m_pSecDesc = Security::CreateApplicationSecurityDescriptor(this); +} + +bool IsPlatformAssembly(LPCSTR szName, DomainAssembly *pDomainAssembly) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(CheckPointer(szName)); + PRECONDITION(CheckPointer(pDomainAssembly)); + } + CONTRACTL_END; + + PEAssembly *pPEAssembly = pDomainAssembly->GetFile(); + + if (strcmp(szName, pPEAssembly->GetSimpleName()) != 0) + { + return false; + } + + DWORD cbPublicKey; + const BYTE *pbPublicKey = static_cast(pPEAssembly->GetPublicKey(&cbPublicKey)); + if (pbPublicKey == nullptr) + { + return false; + } + +#ifdef FEATURE_CORECLR + return StrongNameIsSilverlightPlatformKey(pbPublicKey, cbPublicKey); +#else + return StrongNameIsEcmaKey(pbPublicKey, cbPublicKey); +#endif +} + +void AppDomain::AddAssembly(DomainAssembly * assem) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + { + CrstHolder ch(GetAssemblyListLock()); + + // Attempt to find empty space in assemblies list + DWORD asmCount = m_Assemblies.GetCount_Unlocked(); + for (DWORD i = 0; i < asmCount; ++i) + { + if (m_Assemblies.Get_UnlockedNoReference(i) == NULL) + { + m_Assemblies.Set_Unlocked(i, assem); + return; + } + } + + // If empty space not found, simply add to end of list + IfFailThrow(m_Assemblies.Append_Unlocked(assem)); + } +} + +void AppDomain::RemoveAssembly_Unlocked(DomainAssembly * pAsm) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + _ASSERTE(GetAssemblyListLock()->OwnedByCurrentThread()); + + DWORD asmCount = m_Assemblies.GetCount_Unlocked(); + for (DWORD i = 0; i < asmCount; ++i) + { + if (m_Assemblies.Get_UnlockedNoReference(i) == pAsm) + { + m_Assemblies.Set_Unlocked(i, NULL); + return; + } + } + + _ASSERTE(!"Unreachable"); +} + +BOOL AppDomain::ContainsAssembly(Assembly * assem) +{ + WRAPPER_NO_CONTRACT; + AssemblyIterator i = IterateAssembliesEx((AssemblyIterationFlags)( + kIncludeLoaded | + (assem->IsIntrospectionOnly() ? kIncludeIntrospection : kIncludeExecution))); + CollectibleAssemblyHolder pDomainAssembly; + + while (i.Next(pDomainAssembly.This())) + { + CollectibleAssemblyHolder pAssembly = pDomainAssembly->GetLoadedAssembly(); + if (pAssembly == assem) + return TRUE; + } + + return FALSE; +} + +BOOL AppDomain::HasSetSecurityPolicy() +{ + CONTRACT(BOOL) + { + THROWS; + GC_TRIGGERS; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACT_END; + + GCX_COOP(); + + if (NingenEnabled()) + { + return FALSE; + } + RETURN ((APPDOMAINREF)GetExposedObject())->HasSetPolicy(); +} + +#if defined (FEATURE_LOADER_OPTIMIZATION) && !defined(FEATURE_CORECLR) +// Returns true if the user has declared the desire to load an +// assembly domain-neutral. This is either by specifying System.LoaderOptimizationAttribute +// on the entry routine or the host has set this loader-optimization flag. +BOOL AppDomain::ApplySharePolicy(DomainAssembly *pFile) +{ + CONTRACT(BOOL) + { + PRECONDITION(CheckPointer(pFile)); + THROWS; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACT_END; + + if (!pFile->GetFile()->IsShareable()) + RETURN FALSE; + + if (ApplySharePolicyFlag(pFile)) + RETURN TRUE; + + RETURN FALSE; +} + +BOOL AppDomain::ApplySharePolicyFlag(DomainAssembly *pFile) +{ + CONTRACT(BOOL) + { + PRECONDITION(CheckPointer(pFile)); + THROWS; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACT_END; + + switch(GetSharePolicy()) { + case SHARE_POLICY_ALWAYS: + RETURN (!pFile->MayHaveUnknownDependencies()); + + case SHARE_POLICY_GAC: + RETURN (pFile->IsClosedInGAC()); + + case SHARE_POLICY_NEVER: + RETURN pFile->IsSystem(); + + default: + UNREACHABLE_MSG("Unknown share policy"); + } +} +#endif // FEATURE_LOADER_OPTIMIZATION + +EEClassFactoryInfoHashTable* AppDomain::SetupClassFactHash() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + CrstHolder ch(&m_ReflectionCrst); + + if (m_pRefClassFactHash == NULL) + { + AllocMemHolder pCache(GetLowFrequencyHeap()->AllocMem(S_SIZE_T(sizeof (EEClassFactoryInfoHashTable)))); + EEClassFactoryInfoHashTable *tmp = new (pCache) EEClassFactoryInfoHashTable; + LockOwner lock = {&m_RefClassFactCrst,IsOwnerOfCrst}; + if (!tmp->Init(20, &lock)) + COMPlusThrowOM(); + pCache.SuppressRelease(); + m_pRefClassFactHash = tmp; + } + + return m_pRefClassFactHash; +} + +#ifdef FEATURE_COMINTEROP +DispIDCache* AppDomain::SetupRefDispIDCache() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + CrstHolder ch(&m_ReflectionCrst); + + if (m_pRefDispIDCache == NULL) + { + AllocMemHolder pCache = GetLowFrequencyHeap()->AllocMem(S_SIZE_T(sizeof (DispIDCache))); + + DispIDCache *tmp = new (pCache) DispIDCache; + tmp->Init(); + + pCache.SuppressRelease(); + m_pRefDispIDCache = tmp; + } + + return m_pRefDispIDCache; +} + +#endif // FEATURE_COMINTEROP + +FileLoadLock *FileLoadLock::Create(PEFileListLock *pLock, PEFile *pFile, DomainFile *pDomainFile) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(pLock->HasLock()); + PRECONDITION(pLock->FindFileLock(pFile) == NULL); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + NewHolder result(new FileLoadLock(pLock, pFile, pDomainFile)); + + pLock->AddElement(result); + result->AddRef(); // Add one ref on behalf of the ListLock's reference. The corresponding Release() happens in FileLoadLock::CompleteLoadLevel. + return result.Extract(); +} + +FileLoadLock::~FileLoadLock() +{ + CONTRACTL + { + DESTRUCTOR_CHECK; + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + ((PEFile *) m_pData)->Release(); +} + +DomainFile *FileLoadLock::GetDomainFile() +{ + LIMITED_METHOD_CONTRACT; + return m_pDomainFile; +} + +FileLoadLevel FileLoadLock::GetLoadLevel() +{ + LIMITED_METHOD_CONTRACT; + return m_level; +} + +ADID FileLoadLock::GetAppDomainId() +{ + LIMITED_METHOD_CONTRACT; + return m_AppDomainId; +} + +// Acquire will return FALSE and not take the lock if the file +// has already been loaded to the target level. Otherwise, +// it will return TRUE and take the lock. +// +// Note that the taker must release the lock via IncrementLoadLevel. + +BOOL FileLoadLock::Acquire(FileLoadLevel targetLevel) +{ + WRAPPER_NO_CONTRACT; + + // If we are already loaded to the desired level, the lock is "free". + if (m_level >= targetLevel) + return FALSE; + + if (!DeadlockAwareEnter()) + { + // We failed to get the lock due to a deadlock. + return FALSE; + } + + if (m_level >= targetLevel) + { + Leave(); + return FALSE; + } + + return TRUE; +} + +BOOL FileLoadLock::CanAcquire(FileLoadLevel targetLevel) +{ + // If we are already loaded to the desired level, the lock is "free". + if (m_level >= targetLevel) + return FALSE; + + return CanDeadlockAwareEnter(); +} + +#if !defined(DACCESS_COMPILE) && (defined(LOGGING) || defined(STRESS_LOG)) +static const char *fileLoadLevelName[] = +{ + "CREATE", // FILE_LOAD_CREATE + "BEGIN", // FILE_LOAD_BEGIN + "FIND_NATIVE_IMAGE", // FILE_LOAD_FIND_NATIVE_IMAGE + "VERIFY_NATIVE_IMAGE_DEPENDENCIES", // FILE_LOAD_VERIFY_NATIVE_IMAGE_DEPENDENCIES + "ALLOCATE", // FILE_LOAD_ALLOCATE + "ADD_DEPENDENCIES", // FILE_LOAD_ADD_DEPENDENCIES + "PRE_LOADLIBRARY", // FILE_LOAD_PRE_LOADLIBRARY + "LOADLIBRARY", // FILE_LOAD_LOADLIBRARY + "POST_LOADLIBRARY", // FILE_LOAD_POST_LOADLIBRARY + "EAGER_FIXUPS", // FILE_LOAD_EAGER_FIXUPS + "VTABLE FIXUPS", // FILE_LOAD_VTABLE_FIXUPS + "DELIVER_EVENTS", // FILE_LOAD_DELIVER_EVENTS + "LOADED", // FILE_LOADED + "VERIFY_EXECUTION", // FILE_LOAD_VERIFY_EXECUTION + "ACTIVE", // FILE_ACTIVE +}; +#endif // !DACCESS_COMPILE && (LOGGING || STRESS_LOG) + +BOOL FileLoadLock::CompleteLoadLevel(FileLoadLevel level, BOOL success) +{ + CONTRACTL + { + MODE_ANY; + GC_TRIGGERS; + THROWS; + PRECONDITION(HasLock()); + } + CONTRACTL_END; + + // Increment may happen more than once if reentrancy occurs (e.g. LoadLibrary) + if (level > m_level) + { + // Must complete each level in turn, unless we have an error + CONSISTENCY_CHECK(m_pDomainFile->IsError() || (level == (m_level+1))); + // Remove the lock from the list if the load is completed + if (level >= FILE_ACTIVE) + { + { + GCX_COOP(); + PEFileListLockHolder lock((PEFileListLock*)m_pList); + +#if _DEBUG + BOOL fDbgOnly_SuccessfulUnlink = +#endif + m_pList->Unlink(this); + _ASSERTE(fDbgOnly_SuccessfulUnlink); + + m_pDomainFile->ClearLoading(); + + CONSISTENCY_CHECK(m_dwRefCount >= 2); // Caller (LoadDomainFile) should have 1 refcount and m_pList should have another which was acquired in FileLoadLock::Create. + + m_level = (FileLoadLevel)level; + + // Dev11 bug 236344 + // In AppDomain::IsLoading, if the lock is taken on m_pList and then FindFileLock returns NULL, + // we depend on the DomainFile's load level being up to date. Hence we must update the load + // level while the m_pList lock is held. + if (success) + m_pDomainFile->SetLoadLevel(level); + } + + + Release(); // Release m_pList's refcount on this lock, which was acquired in FileLoadLock::Create + + } + else + { + m_level = (FileLoadLevel)level; + + if (success) + m_pDomainFile->SetLoadLevel(level); + } + +#ifndef DACCESS_COMPILE + switch(level) + { + case FILE_LOAD_ALLOCATE: + case FILE_LOAD_ADD_DEPENDENCIES: + case FILE_LOAD_DELIVER_EVENTS: + case FILE_LOADED: + case FILE_ACTIVE: // The timing of stress logs is not critical, so even for the FILE_ACTIVE stage we need not do it while the m_pList lock is held. + STRESS_LOG4(LF_CLASSLOADER, LL_INFO100, "Completed Load Level %s for DomainFile %p in AD %i - success = %i\n", fileLoadLevelName[level], m_pDomainFile, m_AppDomainId.m_dwId, success); + break; + default: + break; + } +#endif + + return TRUE; + } + else + return FALSE; +} + +void FileLoadLock::SetError(Exception *ex) +{ + CONTRACTL + { + MODE_ANY; + GC_TRIGGERS; + THROWS; + PRECONDITION(CheckPointer(ex)); + PRECONDITION(HasLock()); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + m_cachedHR = ex->GetHR(); + + LOG((LF_LOADER, LL_WARNING, "LOADER: %x:***%s*\t!!!Non-transient error 0x%x\n", + m_pDomainFile->GetAppDomain(), m_pDomainFile->GetSimpleName(), m_cachedHR)); + + m_pDomainFile->SetError(ex); + + CompleteLoadLevel(FILE_ACTIVE, FALSE); +} + +void FileLoadLock::AddRef() +{ + LIMITED_METHOD_CONTRACT; + FastInterlockIncrement((LONG *) &m_dwRefCount); +} + +UINT32 FileLoadLock::Release() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + LONG count = FastInterlockDecrement((LONG *) &m_dwRefCount); + if (count == 0) + delete this; + + return count; +} + +FileLoadLock::FileLoadLock(PEFileListLock *pLock, PEFile *pFile, DomainFile *pDomainFile) + : ListLockEntry(pLock, pFile, "File load lock"), + m_level((FileLoadLevel) (FILE_LOAD_CREATE)), + m_pDomainFile(pDomainFile), + m_cachedHR(S_OK), + m_AppDomainId(pDomainFile->GetAppDomain()->GetId()) +{ + WRAPPER_NO_CONTRACT; + pFile->AddRef(); +} + +void FileLoadLock::HolderLeave(FileLoadLock *pThis) +{ + LIMITED_METHOD_CONTRACT; + pThis->Leave(); +} + + + + + + +// +// Assembly loading: +// +// Assembly loading is carefully layered to avoid deadlocks in the +// presence of circular loading dependencies. +// A LoadLevel is associated with each assembly as it is being loaded. During the +// act of loading (abstractly, increasing its load level), its lock is +// held, and the current load level is stored on the thread. Any +// recursive loads during that period are automatically restricted to +// only partially load the dependent assembly to the same level as the +// caller (or to one short of that level in the presence of a deadlock +// loop.) +// +// Each loading stage must be carfully constructed so that +// this constraint is expected and can be dealt with. +// +// Note that there is one case where this still doesn't handle recursion, and that is the +// security subsytem. The security system runs managed code, and thus must typically fully +// initialize assemblies of permission sets it is trying to use. (And of course, these may be used +// while those assemblies are initializing.) This is dealt with in the historical manner - namely +// the security system passes in a special flag which says that it will deal with null return values +// in the case where a load cannot be safely completed due to such issues. +// + +void AppDomain::LoadSystemAssemblies() +{ + STANDARD_VM_CONTRACT; + + // The only reason to make an assembly a "system assembly" is if the EE is caching + // pointers to stuff in the assembly. Because this is going on, we need to preserve + // the invariant that the assembly is loaded into every app domain. + // + // Right now we have only one system assembly. We shouldn't need to add any more. + + LoadAssembly(NULL, SystemDomain::System()->SystemFile(), FILE_ACTIVE); +} + +FileLoadLevel AppDomain::GetDomainFileLoadLevel(DomainFile *pFile) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END + + LoadLockHolder lock(this); + + FileLoadLock* pLockEntry = (FileLoadLock *) lock->FindFileLock(pFile->GetFile()); + + if (pLockEntry == NULL) + return pFile->GetLoadLevel(); + else + return pLockEntry->GetLoadLevel(); +} + +// This checks if the thread has initiated (or completed) loading at the given level. A false guarantees that +// (a) The current thread (or a thread blocking on the current thread) has not started loading the file +// at the given level, and +// (b) No other thread had started loading the file at this level at the start of this function call. + +// Note that another thread may start loading the file at that level in a race with the completion of +// this function. However, the caller still has the guarantee that such a load started after this +// function was called (and e.g. any state in place before the function call will be seen by the other thread.) +// +// Conversely, a true guarantees that either the current thread has started the load step, or another +// thread has completed the load step. +// + +BOOL AppDomain::IsLoading(DomainFile *pFile, FileLoadLevel level) +{ + // Cheap out + if (pFile->GetLoadLevel() < level) + { + FileLoadLock *pLock = NULL; + { + LoadLockHolder lock(this); + + pLock = (FileLoadLock *) lock->FindFileLock(pFile->GetFile()); + + if (pLock == NULL) + { + // No thread involved with loading + return pFile->GetLoadLevel() >= level; + } + + pLock->AddRef(); + } + + FileLoadLockRefHolder lockRef(pLock); + + if (pLock->Acquire(level)) + { + // We got the lock - therefore no other thread has started this loading step yet. + pLock->Leave(); + return FALSE; + } + + // We didn't get the lock - either this thread is already doing the load, + // or else the load has already finished. + } + return TRUE; +} + +// CheckLoading is a weaker form of IsLoading, which will not block on +// other threads waiting for their status. This is appropriate for asserts. +CHECK AppDomain::CheckLoading(DomainFile *pFile, FileLoadLevel level) +{ + // Cheap out + if (pFile->GetLoadLevel() < level) + { + FileLoadLock *pLock = NULL; + + LoadLockHolder lock(this); + + pLock = (FileLoadLock *) lock->FindFileLock(pFile->GetFile()); + + if (pLock != NULL + && pLock->CanAcquire(level)) + { + // We can get the lock - therefore no other thread has started this loading step yet. + CHECK_FAILF(("Loading step %d has not been initiated yet", level)); + } + + // We didn't get the lock - either this thread is already doing the load, + // or else the load has already finished. + } + + CHECK_OK; +} + +CHECK AppDomain::CheckCanLoadTypes(Assembly *pAssembly) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + CHECK_MSG(CheckValidModule(pAssembly->GetManifestModule()), + "Type loading can occur only when executing in the assembly's app domain"); + CHECK_OK; +} + +CHECK AppDomain::CheckCanExecuteManagedCode(MethodDesc* pMD) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + Module* pModule=pMD->GetModule(); + + CHECK_MSG(CheckValidModule(pModule), + "Managed code can only run when executing in the module's app domain"); + + if (!pMD->IsInterface() || pMD->IsStatic()) //interfaces require no activation for instance methods + { + //cctor could have been interupted by ADU + CHECK_MSG(HasUnloadStarted() || pModule->CheckActivated(), + "Managed code can only run when its module has been activated in the current app domain"); + } + + CHECK_MSG(!IsPassiveDomain() || pModule->CanExecuteCode(), + "Executing managed code from an unsafe assembly in a Passive AppDomain"); + + CHECK_OK; +} + +#endif // !DACCESS_COMPILE + +void AppDomain::LoadDomainFile(DomainFile *pFile, + FileLoadLevel targetLevel) +{ + CONTRACTL + { + if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS; + if (FORBIDGC_LOADER_USE_ENABLED()) GC_NOTRIGGER; else GC_TRIGGERS; + if (FORBIDGC_LOADER_USE_ENABLED()) FORBID_FAULT; else { INJECT_FAULT(COMPlusThrowOM();); } + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + // Quick exit if finished + if (pFile->GetLoadLevel() >= targetLevel) + return; + + // Handle the error case + pFile->ThrowIfError(targetLevel); + + +#ifndef DACCESS_COMPILE + + if (pFile->IsLoading()) + { + GCX_PREEMP(); + + // Load some more if appropriate + LoadLockHolder lock(this); + + FileLoadLock* pLockEntry = (FileLoadLock *) lock->FindFileLock(pFile->GetFile()); + if (pLockEntry == NULL) + { + _ASSERTE (!pFile->IsLoading()); + return; + } + + pLockEntry->AddRef(); + + lock.Release(); + + LoadDomainFile(pLockEntry, targetLevel); + } + +#else // DACCESS_COMPILE + DacNotImpl(); +#endif // DACCESS_COMPILE +} + +#ifndef DACCESS_COMPILE + +FileLoadLevel AppDomain::GetThreadFileLoadLevel() +{ + WRAPPER_NO_CONTRACT; + if (GetThread()->GetLoadLevelLimiter() == NULL) + return FILE_ACTIVE; + else + return (FileLoadLevel)(GetThread()->GetLoadLevelLimiter()->GetLoadLevel()-1); +} + + +Assembly *AppDomain::LoadAssembly(AssemblySpec* pIdentity, + PEAssembly *pFile, + FileLoadLevel targetLevel, + AssemblyLoadSecurity *pLoadSecurity /* = NULL */) +{ + CONTRACT(Assembly *) + { + GC_TRIGGERS; + THROWS; + MODE_ANY; + PRECONDITION(CheckPointer(pFile)); + POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); // May be NULL in recursive load case + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACT_END; + + DomainAssembly *pAssembly = LoadDomainAssembly(pIdentity, pFile, targetLevel, pLoadSecurity); + PREFIX_ASSUME(pAssembly != NULL); + + RETURN pAssembly->GetAssembly(); +} + +#ifndef CROSSGEN_COMPILE +// Thread stress +class LoadDomainAssemblyStress : APIThreadStress +{ +public: + AppDomain *pThis; + AssemblySpec* pSpec; + PEAssembly *pFile; + AssemblyLoadSecurity *pLoadSecurity; + FileLoadLevel targetLevel; + + LoadDomainAssemblyStress(AppDomain *pThis, AssemblySpec* pSpec, PEAssembly *pFile, FileLoadLevel targetLevel, AssemblyLoadSecurity *pLoadSecurity) + : pThis(pThis), pSpec(pSpec), pFile(pFile), pLoadSecurity(pLoadSecurity), targetLevel(targetLevel) {LIMITED_METHOD_CONTRACT;} + + void Invoke() + { + WRAPPER_NO_CONTRACT; + STATIC_CONTRACT_SO_INTOLERANT; + SetupThread(); + pThis->LoadDomainAssembly(pSpec, pFile, targetLevel, pLoadSecurity); + } +}; +#endif // CROSSGEN_COMPILE + +extern BOOL AreSameBinderInstance(ICLRPrivBinder *pBinderA, ICLRPrivBinder *pBinderB); + +DomainAssembly* AppDomain::LoadDomainAssembly( AssemblySpec* pSpec, + PEAssembly *pFile, + FileLoadLevel targetLevel, + AssemblyLoadSecurity *pLoadSecurity /* = NULL */) +{ + STATIC_CONTRACT_THROWS; + + if (pSpec == nullptr) + { + // skip caching, since we don't have anything to base it on + return LoadDomainAssemblyInternal(pSpec, pFile, targetLevel, pLoadSecurity); + } + + DomainAssembly* pRetVal = NULL; + EX_TRY + { + pRetVal = LoadDomainAssemblyInternal(pSpec, pFile, targetLevel, pLoadSecurity); + } + EX_HOOK + { + Exception* pEx=GET_EXCEPTION(); + if (!pEx->IsTransient()) + { +#if defined(FEATURE_CORECLR) + // Setup the binder reference in AssemblySpec from the PEAssembly if one is not already set. + ICLRPrivBinder* pCurrentBindingContext = pSpec->GetBindingContext(); + ICLRPrivBinder* pBindingContextFromPEAssembly = pFile->GetBindingContext(); + + if (pCurrentBindingContext == NULL) + { + // Set the binding context we got from the PEAssembly if AssemblySpec does not + // have that information + _ASSERTE(pBindingContextFromPEAssembly != NULL); + pSpec->SetBindingContext(pBindingContextFromPEAssembly); + } +#if defined(_DEBUG) + else + { + // Binding context in the spec should be the same as the binding context in the PEAssembly + _ASSERTE(AreSameBinderInstance(pCurrentBindingContext, pBindingContextFromPEAssembly)); + } +#endif // _DEBUG +#endif // defined(FEATURE_CORECLR) + + if (!EEFileLoadException::CheckType(pEx)) + { + StackSString name; + pSpec->GetFileOrDisplayName(0, name); + pEx=new EEFileLoadException(name, pEx->GetHR(), NULL, pEx); + AddExceptionToCache(pSpec, pEx); + PAL_CPP_THROW(Exception *, pEx); + } + else + AddExceptionToCache(pSpec, pEx); + } + } + EX_END_HOOK; + + return pRetVal; +} + + +DomainAssembly *AppDomain::LoadDomainAssemblyInternal(AssemblySpec* pIdentity, + PEAssembly *pFile, + FileLoadLevel targetLevel, + AssemblyLoadSecurity *pLoadSecurity /* = NULL */) +{ + CONTRACT(DomainAssembly *) + { + GC_TRIGGERS; + THROWS; + MODE_ANY; + PRECONDITION(CheckPointer(pFile)); + PRECONDITION(CheckPointer(pLoadSecurity, NULL_OK)); + PRECONDITION(pFile->IsSystem() || ::GetAppDomain()==this); + POSTCONDITION(CheckPointer(RETVAL)); + POSTCONDITION(RETVAL->GetLoadLevel() >= GetThreadFileLoadLevel() + || RETVAL->GetLoadLevel() >= targetLevel); + POSTCONDITION(RETVAL->CheckNoError(targetLevel)); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACT_END; + + + DomainAssembly * result; + +#ifndef CROSSGEN_COMPILE + LoadDomainAssemblyStress ts (this, pIdentity, pFile, targetLevel, pLoadSecurity); +#endif + + // Go into preemptive mode since this may take a while. + GCX_PREEMP(); + + // Check for existing fully loaded assembly, or for an assembly which has failed during the loading process. + result = FindAssembly(pFile, FindAssemblyOptions_IncludeFailedToLoad); + + if (result == NULL) + { + // Allocate the DomainAssembly a bit early to avoid GC mode problems. We could potentially avoid + // a rare redundant allocation by moving this closer to FileLoadLock::Create, but it's not worth it. + + NewHolder pDomainAssembly; + pDomainAssembly = new DomainAssembly(this, pFile, pLoadSecurity, this->GetLoaderAllocator()); + + LoadLockHolder lock(this); + + // Find the list lock entry + FileLoadLock * fileLock = (FileLoadLock *)lock->FindFileLock(pFile); + if (fileLock == NULL) + { + // Check again in case we were racing + result = FindAssembly(pFile, FindAssemblyOptions_IncludeFailedToLoad); + if (result == NULL) + { + // We are the first one in - create the DomainAssembly + fileLock = FileLoadLock::Create(lock, pFile, pDomainAssembly); + pDomainAssembly.SuppressRelease(); + } + } + else + { + fileLock->AddRef(); + } + + lock.Release(); + + if (result == NULL) + { + // We pass our ref on fileLock to LoadDomainFile to release. + + // Note that if we throw here, we will poison fileLock with an error condition, + // so it will not be removed until app domain unload. So there is no need + // to release our ref count. + result = (DomainAssembly *)LoadDomainFile(fileLock, targetLevel); + } + else + { + result->EnsureLoadLevel(targetLevel); + } + } + else + result->EnsureLoadLevel(targetLevel); + + // Malformed metadata may contain a Module reference to what is actually + // an Assembly. In this case we need to throw an exception, since returning + // a DomainModule as a DomainAssembly is a type safety violation. + if (!result->IsAssembly()) + { + ThrowHR(COR_E_ASSEMBLYEXPECTED); + } + + // Cache result in all cases, since found pFile could be from a different AssemblyRef than pIdentity + // Do not cache WindowsRuntime assemblies, they are cached in code:CLRPrivTypeCacheWinRT + if ((pIdentity != NULL) && (pIdentity->CanUseWithBindingCache()) && (result->CanUseWithBindingCache())) + GetAppDomain()->AddAssemblyToCache(pIdentity, result); + + RETURN result; +} // AppDomain::LoadDomainAssembly + +#ifdef FEATURE_MULTIMODULE_ASSEMBLIES + +#ifndef CROSSGEN_COMPILE +// Thread stress +class LoadDomainModuleStress : APIThreadStress +{ +public: + AppDomain *pThis; + DomainAssembly *pAssembly; + PEModule *pFile; + FileLoadLevel targetLevel; + + LoadDomainModuleStress(AppDomain *pThis, DomainAssembly *pAssembly, PEModule *pFile, FileLoadLevel targetLevel) + : pThis(pThis), pAssembly(pAssembly), pFile(pFile), targetLevel(targetLevel) {LIMITED_METHOD_CONTRACT;} + + void Invoke() + { + WRAPPER_NO_CONTRACT; + STATIC_CONTRACT_SO_INTOLERANT; + SetupThread(); + pThis->LoadDomainModule(pAssembly, pFile, targetLevel); + } +}; +#endif // CROSSGEN_COMPILE + +DomainModule *AppDomain::LoadDomainModule(DomainAssembly *pAssembly, PEModule *pFile, + FileLoadLevel targetLevel) +{ + CONTRACT(DomainModule *) + { + GC_TRIGGERS; + THROWS; + MODE_ANY; + PRECONDITION(CheckPointer(pAssembly)); + PRECONDITION(CheckPointer(pFile)); + POSTCONDITION(CheckPointer(RETVAL)); + POSTCONDITION(RETVAL->GetLoadLevel() >= GetThreadFileLoadLevel() + || RETVAL->GetLoadLevel() >= targetLevel); + POSTCONDITION(RETVAL->CheckNoError(targetLevel)); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACT_END; + + GCX_PREEMP(); + +#ifndef CROSSGEN_COMPILE + // Thread stress + LoadDomainModuleStress ts (this, pAssembly, pFile, targetLevel); +#endif + + // Check for existing fully loaded assembly + DomainModule *result = pAssembly->FindModule(pFile); + if (result == NULL) + { + LoadLockHolder lock(this); + + // Check again in case we were racing + result = pAssembly->FindModule(pFile); + if (result == NULL) + { + // Find the list lock entry + FileLoadLock *fileLock = (FileLoadLock *) lock->FindFileLock(pFile); + if (fileLock == NULL) + { + // We are the first one in - create the DomainModule + NewHolder pDomainModule(new DomainModule(this, pAssembly, pFile)); + fileLock = FileLoadLock::Create(lock, pFile, pDomainModule); + pDomainModule.SuppressRelease(); + } + else + fileLock->AddRef(); + + lock.Release(); + + // We pass our ref on fileLock to LoadDomainFile to release. + + // Note that if we throw here, we will poison fileLock with an error condition, + // so it will not be removed until app domain unload. So there is no need + // to release our ref count. + + result = (DomainModule *) LoadDomainFile(fileLock, targetLevel); + } + else + { + lock.Release(); + result->EnsureLoadLevel(targetLevel); + } + + } + else + result->EnsureLoadLevel(targetLevel); + + // Malformed metadata may contain an Assembly reference to what is actually + // a Module. In this case we need to throw an exception, since returning a + // DomainAssembly as a DomainModule is a type safety violation. + if (result->IsAssembly()) + { + ThrowHR(COR_E_ASSEMBLY_NOT_EXPECTED); + } + + RETURN result; +} +#endif // FEATURE_MULTIMODULE_ASSEMBLIES + +struct LoadFileArgs +{ + FileLoadLock *pLock; + FileLoadLevel targetLevel; + DomainFile *result; +}; + +#ifndef CROSSGEN_COMPILE +static void LoadDomainFile_Wrapper(void *ptr) +{ + WRAPPER_NO_CONTRACT; + STATIC_CONTRACT_SO_INTOLERANT; + GCX_PREEMP(); + LoadFileArgs *args = (LoadFileArgs *) ptr; + args->result = GetAppDomain()->LoadDomainFile(args->pLock, args->targetLevel); +} +#endif // !CROSSGEN_COMPILE + +DomainFile *AppDomain::LoadDomainFile(FileLoadLock *pLock, FileLoadLevel targetLevel) +{ + CONTRACT(DomainFile *) + { + STANDARD_VM_CHECK; + PRECONDITION(CheckPointer(pLock)); + PRECONDITION(pLock->GetDomainFile()->GetAppDomain() == this); + POSTCONDITION(RETVAL->GetLoadLevel() >= GetThreadFileLoadLevel() + || RETVAL->GetLoadLevel() >= targetLevel); + POSTCONDITION(RETVAL->CheckNoError(targetLevel)); + } + CONTRACT_END; + + + if(!CanLoadCode()) + COMPlusThrow(kAppDomainUnloadedException); + + // Thread stress + APIThreadStress::SyncThreadStress(); + + DomainFile *pFile = pLock->GetDomainFile(); + + // Make sure we release the lock on exit + FileLoadLockRefHolder lockRef(pLock); + + // We need to perform the early steps of loading mscorlib without a domain transition. This is + // important for bootstrapping purposes - we need to get mscorlib at least partially loaded + // into a domain before we can run serialization code to do the transition. + // + // Note that we cannot do this in general for all assemblies, because some of the security computations + // require the managed exposed object, which must be created in the correct app domain. + + if (this != GetAppDomain() + && pFile->GetFile()->IsSystem() + && targetLevel > FILE_LOAD_ALLOCATE) + { + // Re-call the routine with a limited load level. This will cause the first part of the load to + // get performed in the current app domain. + + pLock->AddRef(); + LoadDomainFile(pLock, targetLevel > FILE_LOAD_ALLOCATE ? FILE_LOAD_ALLOCATE : targetLevel); + + // Now continue on to complete the rest of the load, if any. + } + + // Do a quick out check for the already loaded case. + if (pLock->GetLoadLevel() >= targetLevel) + { + pFile->ThrowIfError(targetLevel); + + RETURN pFile; + } + +#ifndef CROSSGEN_COMPILE + // Make sure we are in the right domain. Many of the load operations require the target domain + // to be the current app domain, most notably anything involving managed code or managed object + // creation. + if (this != GetAppDomain() + && (!pFile->GetFile()->IsSystem() || targetLevel > FILE_LOAD_ALLOCATE)) + { + // Transition to the correct app domain and perform the load there. + GCX_COOP(); + + // we will release the lock in the other app domain + lockRef.SuppressRelease(); + + if(!CanLoadCode() || GetDefaultContext() ==NULL) + COMPlusThrow(kAppDomainUnloadedException); + LoadFileArgs args = {pLock, targetLevel, NULL}; + GetThread()->DoADCallBack(this, LoadDomainFile_Wrapper, (void *) &args, ADV_CREATING); + + RETURN args.result; + } +#endif // CROSSGEN_COMPILE + + // Initialize a loading queue. This will hold any loads which are triggered recursively but + // which cannot be immediately satisfied due to anti-deadlock constraints. + + // PendingLoadQueues are allocated on the stack during a load, and + // shared with all nested loads on the same thread. (Note that we won't use + // "candidate" if we are in a recursive load; that's OK since they are cheap to + // construct.) + FileLoadLevel immediateTargetLevel = targetLevel; + { + LoadLevelLimiter limit; + limit.Activate(); + + // We cannot set a target level higher than that allowed by the limiter currently. + // This is because of anti-deadlock constraints. + if (immediateTargetLevel > limit.GetLoadLevel()) + immediateTargetLevel = limit.GetLoadLevel(); + + LOG((LF_LOADER, LL_INFO100, "LOADER: %x:***%s*\t>>>Load initiated, %s/%s\n", + pFile->GetAppDomain(), pFile->GetSimpleName(), + fileLoadLevelName[immediateTargetLevel], fileLoadLevelName[targetLevel])); + + // Now loop and do the load incrementally to the target level. + if (pLock->GetLoadLevel() < immediateTargetLevel) + { + // Thread stress + APIThreadStress::SyncThreadStress(); + + while (pLock->Acquire(immediateTargetLevel)) + { + FileLoadLevel workLevel; + { + FileLoadLockHolder fileLock(pLock); + + // Work level is next step to do + workLevel = (FileLoadLevel)(fileLock->GetLoadLevel()+1); + + // Set up the anti-deadlock constraint: we cannot safely recursively load any assemblies + // on this thread to a higher level than this assembly is being loaded now. + // Note that we do allow work at a parallel level; any deadlocks caused here will + // be resolved by the deadlock detection in the FileLoadLocks. + limit.SetLoadLevel(workLevel); + + LOG((LF_LOADER, + (workLevel == FILE_LOAD_BEGIN + || workLevel == FILE_LOADED + || workLevel == FILE_ACTIVE) + ? LL_INFO10 : LL_INFO1000, + "LOADER: %p:***%s*\t loading at level %s\n", + this, pFile->GetSimpleName(), fileLoadLevelName[workLevel])); + + TryIncrementalLoad(pFile, workLevel, fileLock); + } + TESTHOOKCALL(CompletedFileLoadLevel(GetId().m_dwId,pFile,workLevel)); + } + + if (pLock->GetLoadLevel() == immediateTargetLevel-1) + { + LOG((LF_LOADER, LL_INFO100, "LOADER: %x:***%s*\t<<GetAppDomain(), pFile->GetSimpleName(), + fileLoadLevelName[immediateTargetLevel-1])); + } + } + + LOG((LF_LOADER, LL_INFO100, "LOADER: %x:***%s*\t<<GetAppDomain(), pFile->GetSimpleName(), + fileLoadLevelName[pLock->GetLoadLevel()])); + + } + + // There may have been an error stored on the domain file by another thread, or from a previous load + pFile->ThrowIfError(targetLevel); + + // There are two normal results from the above loop. + // + // 1. We succeeded in loading the file to the current thread's load level. + // 2. We succeeded in loading the file to the current thread's load level - 1, due + // to deadlock condition with another thread loading the same assembly. + // + // Either of these are considered satisfactory results, as code inside a load must expect + // a parial load result. + // + // However, if load level elevation has occurred, then it is possible for a deadlock to + // prevent us from loading an assembly which was loading before the elevation at a radically + // lower level. In such a case, we throw an exception which transiently fails the current + // load, since it is likely we have not satisfied the caller. + // (An alternate, and possibly preferable, strategy here would be for all callers to explicitly + // identify the minimum load level acceptable via CheckLoadDomainFile and throw from there.) + + pFile->RequireLoadLevel((FileLoadLevel)(immediateTargetLevel-1)); + + + RETURN pFile; +} + +void AppDomain::TryIncrementalLoad(DomainFile *pFile, FileLoadLevel workLevel, FileLoadLockHolder &lockHolder) +{ + STANDARD_VM_CONTRACT; + + // This is factored out so we don't call EX_TRY in a loop (EX_TRY can _alloca) + + BOOL released = FALSE; + FileLoadLock* pLoadLock = lockHolder.GetValue(); + + EX_TRY + { +#ifndef FEATURE_CORECLR + // Event Tracing for Windows is used to log data for performance and functional testing purposes. + // The events below are used to measure the performance of two steps in the assembly loader, namely assembly initialization and delivering events. + StackSString ETWAssemblySimpleName; + if (ETW_TRACING_CATEGORY_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_Context, TRACE_LEVEL_INFORMATION, CLR_PRIVATEBINDING_KEYWORD)) + { + LPCUTF8 simpleName = pFile->GetSimpleName(); + ETWAssemblySimpleName.AppendUTF8(simpleName ? simpleName : "NULL"); // Gather data used by ETW events later in this function. + } +#endif // FEATURE_CORECLR + + // Special case: for LoadLibrary, we cannot hold the lock during the + // actual LoadLibrary call, because we might get a callback from _CorDllMain on any + // other thread. (Note that this requires DomainFile's LoadLibrary to be independently threadsafe.) + + if (workLevel == FILE_LOAD_LOADLIBRARY) + { + lockHolder.Release(); + released = TRUE; + } +#ifndef FEATURE_CORECLR + else if (workLevel == FILE_LOAD_DELIVER_EVENTS) + { + FireEtwLoaderDeliverEventsPhaseStart(GetId().m_dwId, ETWLoadContextNotAvailable, ETWFieldUnused, ETWLoaderLoadTypeNotAvailable, NULL, ETWAssemblySimpleName, GetClrInstanceId()); + } +#endif // FEATURE_CORECLR + + // Do the work + TESTHOOKCALL(NextFileLoadLevel(GetId().m_dwId,pFile,workLevel)); +#ifndef FEATURE_CORECLR + if (workLevel == FILE_LOAD_ALLOCATE) + { + FireEtwLoaderAssemblyInitPhaseStart(GetId().m_dwId, ETWLoadContextNotAvailable, ETWFieldUnused, ETWLoaderLoadTypeNotAvailable, NULL, ETWAssemblySimpleName, GetClrInstanceId()); + } +#endif // FEATURE_CORECLR + BOOL success = pFile->DoIncrementalLoad(workLevel); +#ifndef FEATURE_CORECLR + if (workLevel == FILE_LOAD_ALLOCATE) + { + FireEtwLoaderAssemblyInitPhaseEnd(GetId().m_dwId, ETWLoadContextNotAvailable, ETWFieldUnused, ETWLoaderLoadTypeNotAvailable, NULL, ETWAssemblySimpleName, GetClrInstanceId()); + } +#endif // FEATURE_CORECLR + TESTHOOKCALL(CompletingFileLoadLevel(GetId().m_dwId,pFile,workLevel)); + if (released) + { + // Reobtain lock to increment level. (Note that another thread may + // have already done it which is OK. + if (pLoadLock->Acquire(workLevel)) + { + // note lockHolder.Acquire isn't wired up to actually take the lock + lockHolder = pLoadLock; + released = FALSE; + } + } + + if (!released) + { + // Complete the level. + if (pLoadLock->CompleteLoadLevel(workLevel, success) && + pLoadLock->GetLoadLevel()==FILE_LOAD_DELIVER_EVENTS) + { + lockHolder.Release(); + released = TRUE; + pFile->DeliverAsyncEvents(); +#ifndef FEATURE_CORECLR + FireEtwLoaderDeliverEventsPhaseEnd(GetId().m_dwId, ETWLoadContextNotAvailable, ETWFieldUnused, ETWLoaderLoadTypeNotAvailable, NULL, ETWAssemblySimpleName, GetClrInstanceId()); +#endif // FEATURE_CORECLR + }; + } + } + EX_HOOK + { + Exception *pEx = GET_EXCEPTION(); + + + //We will cache this error and wire this load to forever fail, + // unless the exception is transient or the file is loaded OK but just cannot execute + if (!pEx->IsTransient() && !pFile->IsLoaded()) + { + + if (released) + { + // Reobtain lock to increment level. (Note that another thread may + // have already done it which is OK. + if (pLoadLock->Acquire(workLevel)) // note pLockHolder->Acquire isn't wired up to actually take the lock + { + // note lockHolder.Acquire isn't wired up to actually take the lock + lockHolder = pLoadLock; + released = FALSE; + } + } + + if (!released) + { + // Report the error in the lock + pLoadLock->SetError(pEx); + } + + if (!EEFileLoadException::CheckType(pEx)) + EEFileLoadException::Throw(pFile->GetFile(), pEx->GetHR(), pEx); + } + + // Otherwise, we simply abort this load, and can retry later on. + // @todo cleanup: make sure that each level is restartable after an exception, and + // leaves no bad side effects + } + EX_END_HOOK; +} + +// Checks whether the module is valid to be in the given app domain (need not be yet loaded) +CHECK AppDomain::CheckValidModule(Module * pModule) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + if (pModule->FindDomainFile(this) != NULL) + CHECK_OK; + + CCHECK_START + { + Assembly * pAssembly = pModule->GetAssembly(); + + CCHECK(pAssembly->IsDomainNeutral()); +#ifdef FEATURE_LOADER_OPTIMIZATION + Assembly * pSharedAssembly = NULL; + _ASSERTE(this == ::GetAppDomain()); + { + SharedAssemblyLocator locator(pAssembly->GetManifestFile()); + pSharedAssembly = SharedDomain::GetDomain()->FindShareableAssembly(&locator); + } + + CCHECK(pAssembly == pSharedAssembly); +#endif + } + CCHECK_END; + + CHECK_OK; +} + +#ifdef FEATURE_LOADER_OPTIMIZATION +// Loads an existing Module into an AppDomain +// WARNING: this can only be done in a very limited scenario - the Module must be an unloaded domain neutral +// dependency in the app domain in question. Normal code should not call this! +DomainFile *AppDomain::LoadDomainNeutralModuleDependency(Module *pModule, FileLoadLevel targetLevel) +{ + CONTRACT(DomainFile *) + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(::GetAppDomain()==this); + PRECONDITION(CheckPointer(pModule)); + POSTCONDITION(CheckValidModule(pModule)); + POSTCONDITION(CheckPointer(RETVAL)); + POSTCONDITION(RETVAL->GetModule() == pModule); + } + CONTRACT_END; + + DomainFile *pDomainFile = pModule->FindDomainFile(this); + + STRESS_LOG3(LF_CLASSLOADER, LL_INFO100,"LDNMD: DomainFile %p for module %p in AppDomain %i\n",pDomainFile,pModule,GetId().m_dwId); + + if (pDomainFile == NULL) + { + GCX_PREEMP(); + + Assembly *pAssembly = pModule->GetAssembly(); + + DomainAssembly *pDomainAssembly = pAssembly->FindDomainAssembly(this); + if (pDomainAssembly == NULL) + { + AssemblySpec spec(this); + spec.InitializeSpec(pAssembly->GetManifestFile()); + + pDomainAssembly = spec.LoadDomainAssembly(targetLevel); + } + else + { + //if the domain assembly already exists, we need to load it to the target level + pDomainAssembly->EnsureLoadLevel (targetLevel); + } + + if(pAssembly != pDomainAssembly->GetAssembly()) + { + ThrowHR(SECURITY_E_INCOMPATIBLE_SHARE); + } + +#ifdef FEATURE_MULTIMODULE_ASSEMBLIES + if (pModule == pAssembly->GetManifestModule()) + pDomainFile = pDomainAssembly; + else + { + pDomainFile = LoadDomainModule(pDomainAssembly, (PEModule*) pModule->GetFile(), targetLevel); + STRESS_LOG4(LF_CLASSLOADER, LL_INFO100,"LDNMD: DF: for %p[%p/%p] is %p", + pModule,pDomainAssembly,pModule->GetFile(),pDomainFile); + } +#else + _ASSERTE (pModule == pAssembly->GetManifestModule()); + pDomainFile = pDomainAssembly; +#endif + } + else + { + // If the DomainFile already exists, we need to load it to the target level. + pDomainFile->EnsureLoadLevel (targetLevel); + } + + RETURN pDomainFile; +} + +void AppDomain::SetSharePolicy(SharePolicy policy) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + if ((int)policy > SHARE_POLICY_COUNT) + COMPlusThrow(kArgumentException,W("Argument_InvalidValue")); + + // We cannot make all code domain neutral and still provide complete compatibility with regard + // to using custom security policy and assembly evidence. + // + // In particular, if you try to do either of the above AFTER loading a domain neutral assembly + // out of the GAC, we will now throw an exception. The remedy would be to either not use SHARE_POLICY_ALWAYS + // (change LoaderOptimizationMultiDomain to LoaderOptimizationMultiDomainHost), or change the loading order + // in the app domain to do the policy set or evidence load earlier (which BTW will have the effect of + // automatically using MDH rather than MD, for the same result.) + // + // We include a compatibility flag here to preserve old functionality if necessary - this has the effect + // of never using SHARE_POLICY_ALWAYS. + if (policy == SHARE_POLICY_ALWAYS && + (HasSetSecurityPolicy() + || GetCompatibilityFlag(compatOnlyGACDomainNeutral))) + { + // Never share assemblies not in the GAC + policy = SHARE_POLICY_GAC; + } + + if (policy != m_SharePolicy) + { + +#ifdef FEATURE_PREJIT + +#ifdef FEATURE_FUSION + GCX_PREEMP(); + + // Update the native image config flags + FusionBind::SetApplicationContextDWORDProperty(m_pFusionContext, ACTAG_ZAP_CONFIG_FLAGS, + PEFile::GetNativeImageConfigFlags()); +#endif //FEATURE_FUSION + +#endif // FEATURE_PREJIT + + m_SharePolicy = policy; + } + + return; +} + +#ifdef FEATURE_FUSION +BOOL AppDomain::ReduceSharePolicyFromAlways() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + // We may have already committed to always sharing - this is the case if + // we have already loaded non-GAC-bound assemblies as domain neutral. + + if (GetSharePolicy() == SHARE_POLICY_ALWAYS) + { + AppDomain::AssemblyIterator i = IterateAssembliesEx((AssemblyIterationFlags)(kIncludeLoaded | kIncludeLoading | kIncludeExecution)); + CollectibleAssemblyHolder pDomainAssembly; + + // If we have loaded any non-GAC assemblies, we cannot set app domain policy as we have + // already committed to the process-wide policy. + + while (i.Next(pDomainAssembly.This())) + { + if (pDomainAssembly->GetAssembly() && + pDomainAssembly->GetAssembly()->IsDomainNeutral() && + !pDomainAssembly->IsClosedInGAC()) + { + // This assembly has been loaded domain neutral because of SHARE_POLICY_ALWAYS. We + // can't reverse that decision now, so we have to fail the sharing policy change. + return FALSE; + } + } + + // We haven't loaded any non-GAC assemblies yet - scale back to SHARE_POLICY_GAC so + // future non-GAC assemblies won't be loaded as domain neutral. + SetSharePolicy(SHARE_POLICY_GAC); + } + + return TRUE; +} +#endif // FEATURE_FUSION + +AppDomain::SharePolicy AppDomain::GetSharePolicy() +{ + LIMITED_METHOD_CONTRACT; + // If the policy has been explicitly set for + // the domain, use that. + SharePolicy policy = m_SharePolicy; + + // Pick up the a specified config policy + if (policy == SHARE_POLICY_UNSPECIFIED) + policy = (SharePolicy) g_pConfig->DefaultSharePolicy(); + + // Next, honor a host's request for global policy. + if (policy == SHARE_POLICY_UNSPECIFIED) + policy = (SharePolicy) g_dwGlobalSharePolicy; + + // If all else fails, use the hardwired default policy. + if (policy == SHARE_POLICY_UNSPECIFIED) + policy = SHARE_POLICY_DEFAULT; + + return policy; +} +#endif // FEATURE_LOADER_OPTIMIZATION + + +#ifdef FEATURE_CORECLR +void AppDomain::CheckForMismatchedNativeImages(AssemblySpec * pSpec, const GUID * pGuid) +{ + STANDARD_VM_CONTRACT; + + // + // The native images are ever used only for trusted images in CoreCLR. + // We don't wish to open the IL file at runtime so we just forgo any + // eager consistency checking. But we still want to prevent mistmatched + // NGen images from being used. We record all mappings between assembly + // names and MVID, and fail once we detect mismatch. + // + + if (pSpec->IsStrongNamed() && pSpec->HasPublicKey()) + { + pSpec->ConvertPublicKeyToToken(); + } + + // + // CoreCLR binder unifies assembly versions. Ignore assembly version here to + // detect more types of potential mismatches. + // + AssemblyMetaDataInternal * pContext = pSpec->GetContext(); + pContext->usMajorVersion = (USHORT)-1; + pContext->usMinorVersion = (USHORT)-1; + pContext->usBuildNumber = (USHORT)-1; + pContext->usRevisionNumber = (USHORT)-1; + + // Ignore the WinRT type while considering if two assemblies have the same identity. + pSpec->SetWindowsRuntimeType(NULL, NULL); + + CrstHolder ch(&m_DomainCrst); + + const NativeImageDependenciesEntry * pEntry = m_NativeImageDependencies.Lookup(pSpec); + + if (pEntry != NULL) + { + if (*pGuid != pEntry->m_guidMVID) + { + SString msg; + msg.Printf("ERROR: Native images generated against multiple versions of assembly %s. ", pSpec->GetName()); + WszOutputDebugString(msg.GetUnicode()); + COMPlusThrowNonLocalized(kFileLoadException, msg.GetUnicode()); + } + } + else + { + // + // No entry yet - create one + // + AllocMemTracker amTracker; + AllocMemTracker *pamTracker = &amTracker; + + NativeImageDependenciesEntry * pNewEntry = + new (pamTracker->Track(GetLowFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(NativeImageDependenciesEntry))))) + NativeImageDependenciesEntry(); + + pNewEntry->m_AssemblySpec.CopyFrom(pSpec); + pNewEntry->m_AssemblySpec.CloneFieldsToLoaderHeap(AssemblySpec::ALL_OWNED, GetLowFrequencyHeap(), pamTracker); + + pNewEntry->m_guidMVID = *pGuid; + + m_NativeImageDependencies.Add(pNewEntry); + amTracker.SuppressRelease(); + } +} +#endif // FEATURE_CORECLR + + +void AppDomain::SetupSharedStatics() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + +#ifndef CROSSGEN_COMPILE + if (NingenEnabled()) + return; + + LOG((LF_CLASSLOADER, LL_INFO10000, "STATICS: SetupSharedStatics()")); + + // don't do any work in init stage. If not init only do work in non-shared case if are default domain + _ASSERTE(!g_fEEInit); + + // Because we are allocating/referencing objects, need to be in cooperative mode + GCX_COOP(); + + static OBJECTHANDLE hSharedStaticsHandle = NULL; + + if (hSharedStaticsHandle == NULL) { + // Note that there is no race here since the default domain is always set up first + _ASSERTE(IsDefaultDomain()); + + MethodTable *pMT = MscorlibBinder::GetClass(CLASS__SHARED_STATICS); + _ASSERTE(pMT->IsClassPreInited()); + + hSharedStaticsHandle = CreateGlobalHandle(AllocateObject(pMT)); + } + + DomainLocalModule *pLocalModule; + + if (IsSingleAppDomain()) + { + pLocalModule = MscorlibBinder::GetModule()->GetDomainLocalModule(); + } + else + { + pLocalModule = GetDomainLocalBlock()->GetModuleSlot( + MscorlibBinder::GetModule()->GetModuleIndex()); + } + + FieldDesc *pFD = MscorlibBinder::GetField(FIELD__SHARED_STATICS__SHARED_STATICS); + + OBJECTREF* pHandle = (OBJECTREF*) + ((TADDR)pLocalModule->GetPrecomputedGCStaticsBasePointer()+pFD->GetOffset()); + SetObjectReference( pHandle, ObjectFromHandle(hSharedStaticsHandle), this ); + + // This is a convenient place to initialize String.Empty. + // It is treated as intrinsic by the JIT as so the static constructor would never run. + // Leaving it uninitialized would confuse debuggers. + + // String should not have any static constructors. + _ASSERTE(g_pStringClass->IsClassPreInited()); + + FieldDesc * pEmptyStringFD = MscorlibBinder::GetField(FIELD__STRING__EMPTY); + OBJECTREF* pEmptyStringHandle = (OBJECTREF*) + ((TADDR)pLocalModule->GetPrecomputedGCStaticsBasePointer()+pEmptyStringFD->GetOffset()); + SetObjectReference( pEmptyStringHandle, StringObject::GetEmptyString(), this ); +#endif // CROSSGEN_COMPILE +} + +DomainAssembly * AppDomain::FindAssembly(PEAssembly * pFile, FindAssemblyOptions options/* = FindAssemblyOptions_None*/) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + const bool includeFailedToLoad = (options & FindAssemblyOptions_IncludeFailedToLoad) != 0; + + if (pFile->HasHostAssembly()) + { + DomainAssembly * pDA = FindAssembly(pFile->GetHostAssembly()); + if (pDA != nullptr && (pDA->IsLoaded() || (includeFailedToLoad && pDA->IsError()))) + { + return pDA; + } + return nullptr; + } + + AssemblyIterator i = IterateAssembliesEx((AssemblyIterationFlags)( + kIncludeLoaded | + (includeFailedToLoad ? kIncludeFailedToLoad : 0) | + (pFile->IsIntrospectionOnly() ? kIncludeIntrospection : kIncludeExecution))); + CollectibleAssemblyHolder pDomainAssembly; + + while (i.Next(pDomainAssembly.This())) + { + PEFile * pManifestFile = pDomainAssembly->GetFile(); + if (pManifestFile && + !pManifestFile->IsResource() && + pManifestFile->Equals(pFile)) + { + // Caller already has PEAssembly, so we can give DomainAssembly away freely without AddRef + return pDomainAssembly.Extract(); + } + } + return NULL; +} + +static const AssemblyIterationFlags STANDARD_IJW_ITERATOR_FLAGS = + (AssemblyIterationFlags)(kIncludeLoaded | kIncludeLoading | kIncludeExecution | kExcludeCollectible); + +#ifdef FEATURE_MIXEDMODE +Module * AppDomain::GetIJWModule(HMODULE hMod) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + AssemblyIterator i = IterateAssembliesEx(STANDARD_IJW_ITERATOR_FLAGS); + CollectibleAssemblyHolder pDomainAssembly; + + while (i.Next(pDomainAssembly.This())) + { + _ASSERTE(!pDomainAssembly->IsCollectible()); + DomainFile * result = pDomainAssembly->FindIJWModule(hMod); + + if (result == NULL) + continue; + result->EnsureAllocated(); + return result->GetLoadedModule(); + } + + return NULL; +} + +DomainFile * AppDomain::FindIJWDomainFile(HMODULE hMod, const SString & path) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + AssemblyIterator i = IterateAssembliesEx(STANDARD_IJW_ITERATOR_FLAGS); + CollectibleAssemblyHolder pDomainAssembly; + + while (i.Next(pDomainAssembly.This())) + { + _ASSERTE(!pDomainAssembly->IsCollectible()); + if (pDomainAssembly->GetCurrentAssembly() == NULL) + continue; + + DomainFile * result = pDomainAssembly->GetCurrentAssembly()->FindIJWDomainFile(hMod, path); + + if (result != NULL) + return result; + } + + return NULL; +} +#endif // FEATURE_MIXEDMODE + +void AppDomain::SetFriendlyName(LPCWSTR pwzFriendlyName, BOOL fDebuggerCares/*=TRUE*/) +{ + CONTRACTL + { + THROWS; + if (GetThread()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + // Do all computations into a temporary until we're ensured of success + SString tmpFriendlyName; + + + if (pwzFriendlyName) + tmpFriendlyName.Set(pwzFriendlyName); + else + { + // If there is an assembly, try to get the name from it. + // If no assembly, but if it's the DefaultDomain, then give it a name + + if (m_pRootAssembly) + { + tmpFriendlyName.SetUTF8(m_pRootAssembly->GetSimpleName()); + + SString::Iterator i = tmpFriendlyName.End(); + if (tmpFriendlyName.FindBack(i, '.')) + tmpFriendlyName.Truncate(i); + } + else + { + if (IsDefaultDomain()) + tmpFriendlyName.Set(DEFAULT_DOMAIN_FRIENDLY_NAME); + + // This is for the profiler - if they call GetFriendlyName on an AppdomainCreateStarted + // event, then we want to give them a temporary name they can use. + else if (GetId().m_dwId != 0) + { + tmpFriendlyName.Clear(); + tmpFriendlyName.Printf(W("%s %d"), OTHER_DOMAIN_FRIENDLY_NAME_PREFIX, GetId().m_dwId); + } + } + + } + + tmpFriendlyName.Normalize(); + + + m_friendlyName = tmpFriendlyName; + m_friendlyName.Normalize(); + + if(g_pDebugInterface) + { + // update the name in the IPC publishing block + if (SUCCEEDED(g_pDebugInterface->UpdateAppDomainEntryInIPC(this))) + { + // inform the attached debugger that the name of this appdomain has changed. + if (IsDebuggerAttached() && fDebuggerCares) + g_pDebugInterface->NameChangeEvent(this, NULL); + } + } +} + +void AppDomain::ResetFriendlyName(BOOL fDebuggerCares/*=TRUE*/) +{ + WRAPPER_NO_CONTRACT; + SetFriendlyName(NULL, fDebuggerCares); +} + +LPCWSTR AppDomain::GetFriendlyName(BOOL fDebuggerCares/*=TRUE*/) +{ + CONTRACT (LPCWSTR) + { + THROWS; + if (GetThread()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} + MODE_ANY; + POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACT_END; + +#if _DEBUG + // Handle NULL this pointer - this happens sometimes when printing log messages + // but in general shouldn't occur in real code + if (this == NULL) + RETURN NULL; +#endif // _DEBUG + + if (m_friendlyName.IsEmpty()) + SetFriendlyName(NULL, fDebuggerCares); + + RETURN m_friendlyName; +} + +LPCWSTR AppDomain::GetFriendlyNameForLogging() +{ + CONTRACT(LPWSTR) + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + POSTCONDITION(CheckPointer(RETVAL,NULL_OK)); + } + CONTRACT_END; +#if _DEBUG + // Handle NULL this pointer - this happens sometimes when printing log messages + // but in general shouldn't occur in real code + if (this == NULL) + RETURN NULL; +#endif // _DEBUG + RETURN (m_friendlyName.IsEmpty() ?W(""):(LPCWSTR)m_friendlyName); +} + +LPCWSTR AppDomain::GetFriendlyNameForDebugger() +{ + CONTRACT (LPCWSTR) + { + NOTHROW; + if (GetThread()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} + MODE_ANY; + POSTCONDITION(CheckPointer(RETVAL)); + } + CONTRACT_END; + + + if (m_friendlyName.IsEmpty()) + { + BOOL fSuccess = FALSE; + + EX_TRY + { + SetFriendlyName(NULL); + + fSuccess = TRUE; + } + EX_CATCH + { + // Gobble all exceptions. + } + EX_END_CATCH(SwallowAllExceptions); + + if (!fSuccess) + { + RETURN W(""); + } + } + + RETURN m_friendlyName; +} + + +#endif // !DACCESS_COMPILE + +#ifdef DACCESS_COMPILE + +PVOID AppDomain::GetFriendlyNameNoSet(bool* isUtf8) +{ + SUPPORTS_DAC; + + if (!m_friendlyName.IsEmpty()) + { + *isUtf8 = false; + return m_friendlyName.DacGetRawContent(); + } + else if (m_pRootAssembly) + { + *isUtf8 = true; + return (PVOID)m_pRootAssembly->GetSimpleName(); + } + else if (dac_cast(this) == + dac_cast(SystemDomain::System()->DefaultDomain())) + { + *isUtf8 = false; + return (PVOID)DEFAULT_DOMAIN_FRIENDLY_NAME; + } + else + { + return NULL; + } +} + +#endif // DACCESS_COMPILE + +void AppDomain::CacheStringsForDAC() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + // + // If the application base, private bin paths, and configuration file are + // available, cache them so DAC can read them out of memory + // +#ifdef FEATURE_FUSION + if (m_pFusionContext) + { + CQuickBytes qb; + LPWSTR ssz = (LPWSTR) qb.AllocThrows(MAX_URL_LENGTH * sizeof(WCHAR)); + + DWORD dwSize; + + // application base + ssz[0] = '\0'; + dwSize = MAX_URL_LENGTH * sizeof(WCHAR); + m_pFusionContext->Get(ACTAG_APP_BASE_URL, ssz, &dwSize, 0); + m_applicationBase.Set(ssz); + + // private bin paths + ssz[0] = '\0'; + dwSize = MAX_URL_LENGTH * sizeof(WCHAR); + m_pFusionContext->Get(ACTAG_APP_PRIVATE_BINPATH, ssz, &dwSize, 0); + m_privateBinPaths.Set(ssz); + + // configuration file + ssz[0] = '\0'; + dwSize = MAX_URL_LENGTH * sizeof(WCHAR); + m_pFusionContext->Get(ACTAG_APP_CONFIG_FILE, ssz, &dwSize, 0); + m_configFile.Set(ssz); + } +#endif // FEATURE_FUSION +} + +#ifndef DACCESS_COMPILE + +BOOL AppDomain::AddFileToCache(AssemblySpec* pSpec, PEAssembly *pFile, BOOL fAllowFailure) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(CheckPointer(pSpec)); + // Hosted fusion binder makes an exception here, so we cannot assert. + //PRECONDITION(pSpec->CanUseWithBindingCache()); + //PRECONDITION(pFile->CanUseWithBindingCache()); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + CrstHolder holder(&m_DomainCacheCrst); + // !!! suppress exceptions + if(!m_AssemblyCache.StoreFile(pSpec, pFile) && !fAllowFailure) + { + // TODO: Disabling the below assertion as currently we experience + // inconsistency on resolving the Microsoft.Office.Interop.MSProject.dll + // This causes below assertion to fire and crashes the VS. This issue + // is being tracked with Dev10 Bug 658555. Brought back it when this bug + // is fixed. + // _ASSERTE(FALSE); + + EEFileLoadException::Throw(pSpec, FUSION_E_CACHEFILE_FAILED, NULL); + } + + return TRUE; +} + +BOOL AppDomain::AddAssemblyToCache(AssemblySpec* pSpec, DomainAssembly *pAssembly) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(CheckPointer(pSpec)); + PRECONDITION(CheckPointer(pAssembly)); + PRECONDITION(pSpec->CanUseWithBindingCache()); + PRECONDITION(pAssembly->CanUseWithBindingCache()); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + CrstHolder holder(&m_DomainCacheCrst); + // !!! suppress exceptions + BOOL bRetVal = m_AssemblyCache.StoreAssembly(pSpec, pAssembly); +#ifdef FEATURE_FUSION + // check for context propagation + if (bRetVal && pSpec->GetParentLoadContext() == LOADCTX_TYPE_LOADFROM && pAssembly->GetFile()->GetLoadContext() == LOADCTX_TYPE_DEFAULT) + { + // LoadFrom propagation occurred, store it in a way reachable by Load() (the "post-policy" one) + AssemblySpec loadSpec; + loadSpec.CopyFrom(pSpec); + loadSpec.SetParentAssembly(NULL); + bRetVal = m_AssemblyCache.StoreAssembly(&loadSpec, pAssembly); + } +#endif + return bRetVal; +} + +BOOL AppDomain::AddExceptionToCache(AssemblySpec* pSpec, Exception *ex) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(CheckPointer(pSpec)); + PRECONDITION(pSpec->CanUseWithBindingCache()); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + if (ex->IsTransient()) + return TRUE; + + CrstHolder holder(&m_DomainCacheCrst); + // !!! suppress exceptions + return m_AssemblyCache.StoreException(pSpec, ex); +} + +void AppDomain::AddUnmanagedImageToCache(LPCWSTR libraryName, HMODULE hMod) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(CheckPointer(libraryName)); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + if (libraryName) + { + AssemblySpec spec; + spec.SetCodeBase(libraryName); + m_UnmanagedCache.InsertEntry(&spec, hMod); + } + return ; +} + + +HMODULE AppDomain::FindUnmanagedImageInCache(LPCWSTR libraryName) +{ + CONTRACT(HMODULE) + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(CheckPointer(libraryName,NULL_OK)); + POSTCONDITION(CheckPointer(RETVAL,NULL_OK)); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACT_END; + if(libraryName == NULL) RETURN NULL; + + AssemblySpec spec; + spec.SetCodeBase(libraryName); + RETURN (HMODULE) m_UnmanagedCache.LookupEntry(&spec, 0); +} + + +BOOL AppDomain::IsCached(AssemblySpec *pSpec) +{ + WRAPPER_NO_CONTRACT; + + // Check to see if this fits our rather loose idea of a reference to mscorlib. + // If so, don't use fusion to bind it - do it ourselves. + if (pSpec->IsMscorlib()) + return TRUE; + + return m_AssemblyCache.Contains(pSpec); +} + + +PEAssembly* AppDomain::FindCachedFile(AssemblySpec* pSpec, BOOL fThrow /*=TRUE*/) +{ + CONTRACTL + { + if (fThrow) { + GC_TRIGGERS; + THROWS; + } + else { + GC_NOTRIGGER; + NOTHROW; + } + MODE_ANY; + } + CONTRACTL_END; + + // Check to see if this fits our rather loose idea of a reference to mscorlib. + // If so, don't use fusion to bind it - do it ourselves. + if (fThrow && pSpec->IsMscorlib()) + { + CONSISTENCY_CHECK(SystemDomain::System()->SystemAssembly() != NULL); + PEAssembly *pFile = SystemDomain::System()->SystemFile(); + pFile->AddRef(); + return pFile; + } + + return m_AssemblyCache.LookupFile(pSpec, fThrow); +} + + +BOOL AppDomain::PostBindResolveAssembly(AssemblySpec *pPrePolicySpec, + AssemblySpec *pPostPolicySpec, + HRESULT hrBindResult, + AssemblySpec **ppFailedSpec) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; + PRECONDITION(CheckPointer(pPrePolicySpec)); + PRECONDITION(CheckPointer(pPostPolicySpec)); + PRECONDITION(CheckPointer(ppFailedSpec)); + + BOOL fFailure = TRUE; + *ppFailedSpec = pPrePolicySpec; + +#ifdef FEATURE_FUSION + // Fusion policy could have been applied, + // so failed assembly could be not exactly what we ordered + + IAssemblyName *pIPostPolicyName = pPrePolicySpec->GetNameAfterPolicy(); + + // Get post-policy assembly name + if (pIPostPolicyName != NULL) + { + pPostPolicySpec->InitializeSpec(pIPostPolicyName, + NULL, + pPrePolicySpec->IsIntrospectionOnly()); + pPrePolicySpec->ReleaseNameAfterPolicy(); + + if (!pPostPolicySpec->CompareEx(pPrePolicySpec)) + { + *ppFailedSpec = pPostPolicySpec; + } + } +#endif //FEATURE_FUSION + + PEAssemblyHolder result; + + if ((EEFileLoadException::GetFileLoadKind(hrBindResult) == kFileNotFoundException) || + (hrBindResult == FUSION_E_REF_DEF_MISMATCH) || + (hrBindResult == FUSION_E_INVALID_NAME)) + { + result = TryResolveAssembly(*ppFailedSpec, FALSE /* fPreBind */); + + if (result != NULL && pPrePolicySpec->CanUseWithBindingCache() && result->CanUseWithBindingCache()) + { + fFailure = FALSE; + + // Given the post-policy resolve event construction of the CLR binder, + // chained managed resolve events can race with each other, therefore we do allow + // the adding of the result to fail. Checking for already chached specs + // is not an option as it would introduce another race window. + // The binder does a re-fetch of the + // orignal binding spec and therefore will not cause inconsistency here. + // For the purposes of the resolve event, failure to add to the cache still is a success. + AddFileToCache(pPrePolicySpec, result, TRUE /* fAllowFailure */); + if (*ppFailedSpec != pPrePolicySpec && pPostPolicySpec->CanUseWithBindingCache()) + { + AddFileToCache(pPostPolicySpec, result, TRUE /* fAllowFailure */ ); + } + } + } + + return fFailure; +} + +//---------------------------------------------------------------------------------------- +// Helper class for hosted binder + +class PEAssemblyAsPrivAssemblyInfo : public IUnknownCommon +{ +public: + //------------------------------------------------------------------------------------ + // Ctor + + PEAssemblyAsPrivAssemblyInfo(PEAssembly *pPEAssembly) + { + LIMITED_METHOD_CONTRACT; + STATIC_CONTRACT_THROWS; + + if (pPEAssembly == nullptr) + ThrowHR(E_UNEXPECTED); + + pPEAssembly->AddRef(); + m_pPEAssembly = pPEAssembly; + } + + //------------------------------------------------------------------------------------ + // ICLRPrivAssemblyInfo methods + + //------------------------------------------------------------------------------------ + STDMETHOD(GetAssemblyName)( + __in DWORD cchBuffer, + __out_opt LPDWORD pcchBuffer, + __out_ecount_part_opt(cchBuffer, *pcchBuffer) LPWSTR wzBuffer) + { + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + + if ((cchBuffer == 0) != (wzBuffer == nullptr)) + { + return E_INVALIDARG; + } + + LPCUTF8 szName = m_pPEAssembly->GetSimpleName(); + + bool bIsAscii; + DWORD cchName; + IfFailRet(FString::Utf8_Unicode_Length(szName, &bIsAscii, &cchName)); + + if (cchBuffer < cchName + 1) + { + if (pcchBuffer != nullptr) + { + *pcchBuffer = cchName + 1; + } + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + else + { + IfFailRet(FString::Utf8_Unicode(szName, bIsAscii, wzBuffer, cchBuffer)); + if (pcchBuffer != nullptr) + { + *pcchBuffer = cchName; + } + return S_OK; + } + } + + //------------------------------------------------------------------------------------ + STDMETHOD(GetAssemblyVersion)( + USHORT *pMajor, + USHORT *pMinor, + USHORT *pBuild, + USHORT *pRevision) + { + WRAPPER_NO_CONTRACT; + return m_pPEAssembly->GetVersion(pMajor, pMinor, pBuild, pRevision); + } + + //------------------------------------------------------------------------------------ + STDMETHOD(GetAssemblyPublicKey)( + DWORD cbBuffer, + LPDWORD pcbBuffer, + BYTE *pbBuffer) + { + STATIC_CONTRACT_LIMITED_METHOD; + STATIC_CONTRACT_CAN_TAKE_LOCK; + + VALIDATE_PTR_RET(pcbBuffer); + VALIDATE_CONDITION((pbBuffer == nullptr) == (cbBuffer == 0), return E_INVALIDARG); + + HRESULT hr = S_OK; + + EX_TRY + { + // Note: PEAssembly::GetPublicKey will return bogus data pointer when *pcbBuffer == 0 + LPCVOID pbKey = m_pPEAssembly->GetPublicKey(pcbBuffer); + + if (*pcbBuffer != 0) + { + if (pbBuffer != nullptr && cbBuffer >= *pcbBuffer) + { + memcpy(pbBuffer, pbKey, *pcbBuffer); + hr = S_OK; + } + else + { + hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + } + else + { + hr = S_FALSE; // ==> No public key + } + } + EX_CATCH_HRESULT(hr); + + return hr; + } + +private: + ReleaseHolder m_pPEAssembly; +}; + +//----------------------------------------------------------------------------------------------------------------- +static HRESULT VerifyBindHelper( + ICLRPrivAssembly *pPrivAssembly, + IAssemblyName *pAssemblyName, + PEAssembly *pPEAssembly) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; + + HRESULT hr = S_OK; + // Create an ICLRPrivAssemblyInfo to call to ICLRPrivAssembly::VerifyBind + NewHolder pPrivAssemblyInfoImpl = new PEAssemblyAsPrivAssemblyInfo(pPEAssembly); + ReleaseHolder pPrivAssemblyInfo; + IfFailRet(pPrivAssemblyInfoImpl->QueryInterface(__uuidof(ICLRPrivAssemblyInfo), (LPVOID *)&pPrivAssemblyInfo)); + pPrivAssemblyInfoImpl.SuppressRelease(); + + // Call VerifyBind to give the host a chance to reject the bind based on assembly image contents. + IfFailRet(pPrivAssembly->VerifyBind(pAssemblyName, pPrivAssembly, pPrivAssemblyInfo)); + + return hr; +} + +//----------------------------------------------------------------------------------------------------------------- +HRESULT AppDomain::BindAssemblySpecForHostedBinder( + AssemblySpec * pSpec, + IAssemblyName * pAssemblyName, + ICLRPrivBinder * pBinder, + PEAssembly ** ppAssembly) +{ + STANDARD_VM_CONTRACT; + + PRECONDITION(CheckPointer(pSpec)); + PRECONDITION(pSpec->GetAppDomain() == this); + PRECONDITION(CheckPointer(ppAssembly)); + PRECONDITION(pSpec->GetCodeBase() == nullptr); + + HRESULT hr = S_OK; + +#ifdef FEATURE_FUSION + StackSString wszAssemblyName; + + if (fusion::logging::LoggingEnabled()) + { // Don't perform computation if logging is not enabled. + FusionBind::GetAssemblyNameDisplayName(pAssemblyName, wszAssemblyName, ASM_DISPLAYF_FULL); + } + + // Fire ETW Start event. + FireEtwBindingPhaseStart( + GetId().m_dwId, LOADCTX_TYPE_HOSTED, ETWFieldUnused, ETWLoaderLoadTypeNotAvailable, + pSpec->m_wszCodeBase, wszAssemblyName.GetUnicode(), GetClrInstanceId()); +#endif + + // The Fusion binder can throw (to preserve compat, since it will actually perform an assembly + // load as part of it's bind), so we need to be careful here to catch any FileNotFoundException + // objects if fThrowIfNotFound is false. + ReleaseHolder pPrivAssembly; + + // We return HRESULTs here on failure instead of throwing as failures here are not necessarily indicative + // of an actual application problem. Returning an error code is substantially faster than throwing, and + // should be used when possible. + IfFailRet(pBinder->BindAssemblyByName(pAssemblyName, &pPrivAssembly)); + + IfFailRet(BindHostedPrivAssembly(nullptr, pPrivAssembly, pAssemblyName, ppAssembly)); + +#ifdef FEATURE_FUSION + // Fire ETW End event. + FireEtwBindingPhaseEnd( + GetId().m_dwId, LOADCTX_TYPE_HOSTED, ETWFieldUnused, ETWLoaderLoadTypeNotAvailable, + pSpec->m_wszCodeBase, wszAssemblyName.GetUnicode(), GetClrInstanceId()); + + #endif + + return S_OK; +} + +//----------------------------------------------------------------------------------------------------------------- +HRESULT +AppDomain::BindHostedPrivAssembly( + PEAssembly * pParentAssembly, + ICLRPrivAssembly * pPrivAssembly, + IAssemblyName * pAssemblyName, + PEAssembly ** ppAssembly, + BOOL fIsIntrospectionOnly) // = FALSE +{ + STANDARD_VM_CONTRACT; + + PRECONDITION(CheckPointer(pPrivAssembly)); + PRECONDITION(CheckPointer(ppAssembly)); + + HRESULT hr = S_OK; + + *ppAssembly = nullptr; + + // See if result has been previously loaded. + { + DomainAssembly* pDomainAssembly = FindAssembly(pPrivAssembly); + if (pDomainAssembly != nullptr) + { + *ppAssembly = clr::SafeAddRef(pDomainAssembly->GetFile()); + } + } + + if (*ppAssembly != nullptr) + { // Already exists: ask the binder to verify and return the assembly. + return VerifyBindHelper(pPrivAssembly, pAssemblyName, *ppAssembly); + } + + // Get the IL PEFile. + PEImageHolder pPEImageIL; + { + // Does not already exist, so get the resource for the assembly and load it. + DWORD dwImageType; + ReleaseHolder pIResourceIL; + + IfFailRet(pPrivAssembly->GetImageResource(ASSEMBLY_IMAGE_TYPE_IL, &dwImageType, &pIResourceIL)); + _ASSERTE(dwImageType == ASSEMBLY_IMAGE_TYPE_IL); + + pPEImageIL = PEImage::OpenImage(pIResourceIL, MDInternalImport_Default); + } + + // See if an NI is available. + DWORD dwAvailableImages; + IfFailRet(pPrivAssembly->GetAvailableImageTypes(&dwAvailableImages)); + _ASSERTE(dwAvailableImages & ASSEMBLY_IMAGE_TYPE_IL); // Just double checking that IL bit is always set. + + // Get the NI PEFile if available. + PEImageHolder pPEImageNI; + if (dwAvailableImages & ASSEMBLY_IMAGE_TYPE_NATIVE) + { + DWORD dwImageType; + ReleaseHolder pIResourceNI; + + IfFailRet(pPrivAssembly->GetImageResource(ASSEMBLY_IMAGE_TYPE_NATIVE, &dwImageType, &pIResourceNI)); + _ASSERTE(dwImageType == ASSEMBLY_IMAGE_TYPE_NATIVE || FAILED(hr)); + + pPEImageNI = PEImage::OpenImage(pIResourceNI, MDInternalImport_TrustedNativeImage); + } + _ASSERTE(pPEImageIL != nullptr); + + // Create a PEAssembly using the IL and NI images. + PEAssemblyHolder pPEAssembly = PEAssembly::Open(pParentAssembly, pPEImageIL, pPEImageNI, pPrivAssembly, fIsIntrospectionOnly); + +#ifdef FEATURE_FUSION + // Ensure that the assembly found can be loaded for execution in the process. + if (!fIsIntrospectionOnly) + IfFailRet(RuntimeIsValidAssemblyOnThisPlatform_CheckProcessorArchitecture(pPEAssembly->GetFusionProcessorArchitecture(), FALSE)); +#endif + + // Ask the binder to verify. + IfFailRet(VerifyBindHelper(pPrivAssembly, pAssemblyName, pPEAssembly)); + + // The result. + *ppAssembly = pPEAssembly.Extract(); + + return S_OK; +} // AppDomain::BindHostedPrivAssembly + +//--------------------------------------------------------------------------------------------------------------------- +PEAssembly * AppDomain::BindAssemblySpec( + AssemblySpec * pSpec, + BOOL fThrowOnFileNotFound, + BOOL fRaisePrebindEvents, + StackCrawlMark * pCallerStackMark, + AssemblyLoadSecurity * pLoadSecurity, + BOOL fUseHostBinderIfAvailable) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; + PRECONDITION(CheckPointer(pSpec)); + PRECONDITION(pSpec->GetAppDomain() == this); + PRECONDITION(this==::GetAppDomain()); + + GCX_PREEMP(); + + BOOL fForceReThrow = FALSE; + +#if defined(FEATURE_APPX_BINDER) + // + // If there is a host binder available and this is an unparented bind within the + // default load context, then the bind will be delegated to the domain-wide host + // binder. If there is a parent assembly, then a bind will occur only if it has + // an associated ICLRPrivAssembly to serve as the binder. + // + // fUseHostBinderIfAvailable can be false if this method is called by + // CLRPrivBinderFusion::BindAssemblyByName, which explicitly indicates that it + // wants to use the fusion binder. + // + + if (AppX::IsAppXProcess() && + fUseHostBinderIfAvailable && + ( + ( pSpec->HasParentAssembly() + ? // Parent assembly is hosted + pSpec->GetParentAssembly()->GetFile()->HasHostAssembly() + : // Non-parented default context bind + ( HasLoadContextHostBinder() && + !pSpec->IsIntrospectionOnly() + ) + ) || + (pSpec->GetHostBinder() != nullptr) + ) + ) + { + HRESULT hr = S_OK; + + if (pSpec->GetCodeBase() != nullptr) + { // LoadFrom is not supported in AppX (we should never even get here) + IfFailThrow(E_INVALIDARG); + } + + // Get the assembly display name. + ReleaseHolder pAssemblyName; + IfFailThrow(pSpec->CreateFusionName(&pAssemblyName, TRUE, TRUE)); + + // Create new binding scope for fusion logging. + fusion::logging::BindingScope defaultScope(pAssemblyName, FUSION_BIND_LOG_CATEGORY_DEFAULT); + + PEAssemblyHolder pAssembly; + EX_TRY + { + // If there is a specified binder, then it is used. + // Otherwise if there exist a parent assembly, then it provides the binding context + // Otherwise the domain's root-level binder is used. + ICLRPrivBinder * pBinder = nullptr; + + if (pSpec->GetHostBinder() != nullptr) + { + pBinder = pSpec->GetHostBinder(); + } + else + { + PEAssembly * pParentAssembly = + (pSpec->GetParentAssembly() == nullptr) ? nullptr : pSpec->GetParentAssembly()->GetFile(); + + if ((pParentAssembly != nullptr) && (pParentAssembly->HasHostAssembly())) + { + BOOL fMustUseOriginalLoadContextBinder = FALSE; + if (pSpec->IsContentType_WindowsRuntime()) + { + // Ugly, but we need to handle Framework assemblies that contain WinRT type references, + // and the Fusion binder won't resolve these in AppX processes. The shareable flag is currently + // a reasonable proxy for these cases. (It also catches first party WinMD files, but depedencies + // from those can also be resolved by the original load context binder). + // TODO! Update the fusion binder to resolve WinMD references correctly. + IfFailThrow(pParentAssembly->GetHostAssembly()->IsShareable(&fMustUseOriginalLoadContextBinder)); + } + + if (fMustUseOriginalLoadContextBinder) + { + pBinder = GetLoadContextHostBinder(); + } + else + { + pBinder = pParentAssembly->GetHostAssembly(); + } + } + else + { + pBinder = GetCurrentLoadContextHostBinder(); + } + } + _ASSERTE(pBinder != nullptr); + + hr = BindAssemblySpecForHostedBinder(pSpec, pAssemblyName, pBinder, &pAssembly); + if (FAILED(hr)) + { + goto EndTry1; + } +EndTry1:; + } + // The combination of this conditional catch/ the following if statement which will throw reduces the count of exceptions + // thrown in scenarios where the exception does not escape the method. We cannot get rid of the try/catch block, as + // there are cases within some of the clrpriv binder's which throw. + // Note: In theory, FileNotFound should always come here as HRESULT, never as exception. + EX_CATCH_HRESULT_IF(hr, + !fThrowOnFileNotFound && Assembly::FileNotFound(hr)) + + if (FAILED(hr) && (fThrowOnFileNotFound || !Assembly::FileNotFound(hr))) + { + if (Assembly::FileNotFound(hr)) + { + _ASSERTE(fThrowOnFileNotFound); + // Uses defaultScope + EEFileLoadException::Throw(pSpec, fusion::logging::GetCurrentFusionBindLog(), hr); + } + if ((hr == CLR_E_BIND_UNRECOGNIZED_IDENTITY_FORMAT) && pSpec->IsContentType_WindowsRuntime()) + { // Error returned e.g. for WinRT type name without namespace + if (fThrowOnFileNotFound) + { // Throw ArgumentException (with the HRESULT) wrapped by TypeLoadException to give user type name for diagnostics + // Note: TypeLoadException is equivalent of FileNotFound in WinRT world + EEMessageException ex(hr); + EX_THROW_WITH_INNER(EETypeLoadException, (pSpec->GetWinRtTypeNamespace(), pSpec->GetWinRtTypeClassName(), nullptr, nullptr, IDS_EE_WINRT_LOADFAILURE), &ex); + } + } + else + { + IfFailThrow(hr); + } + } + + _ASSERTE((pAssembly != nullptr) || (FAILED(hr) && !fThrowOnFileNotFound)); + return pAssembly.Extract(); + } + else +#endif // FEATURE_APPX_BINDER +#if defined(FEATURE_COMINTEROP) + // Handle WinRT assemblies in the classic/hybrid scenario. If this is an AppX process, + // then this case will be handled by the previous block as part of the full set of + // available binding hosts. +#ifndef FEATURE_APPX_BINDER + if (pSpec->IsContentType_WindowsRuntime()) +#else + if (!AppX::IsAppXProcess() && pSpec->IsContentType_WindowsRuntime()) +#endif + { + HRESULT hr = S_OK; + + // Get the assembly display name. + ReleaseHolder pAssemblyName; + + IfFailThrow(pSpec->CreateFusionName(&pAssemblyName, TRUE, TRUE)); + +#ifdef FEATURE_FUSION + // Create new binding scope for fusion logging. + fusion::logging::BindingScope defaultScope(pAssemblyName, FUSION_BIND_LOG_CATEGORY_DEFAULT); +#endif + + PEAssemblyHolder pAssembly; + + EX_TRY + { + hr = BindAssemblySpecForHostedBinder(pSpec, pAssemblyName, m_pWinRtBinder, &pAssembly); + if (FAILED(hr)) + goto EndTry2; // Goto end of try block. +EndTry2:; + } + // The combination of this conditional catch/ the following if statement which will throw reduces the count of exceptions + // thrown in scenarios where the exception does not escape the method. We cannot get rid of the try/catch block, as + // there are cases within some of the clrpriv binder's which throw. + // Note: In theory, FileNotFound should always come here as HRESULT, never as exception. + EX_CATCH_HRESULT_IF(hr, + !fThrowOnFileNotFound && Assembly::FileNotFound(hr)) + + if (FAILED(hr) && (fThrowOnFileNotFound || !Assembly::FileNotFound(hr))) + { + if (Assembly::FileNotFound(hr)) + { + _ASSERTE(fThrowOnFileNotFound); + // Uses defaultScope +#ifdef FEATURE_FUSION + EEFileLoadException::Throw(pSpec, fusion::logging::GetCurrentFusionBindLog(), hr); +#else + EEFileLoadException::Throw(pSpec, hr); +#endif // FEATURE_FUSION + } + + // WinRT type bind failures + _ASSERTE(pSpec->IsContentType_WindowsRuntime()); + if (hr == HRESULT_FROM_WIN32(APPMODEL_ERROR_NO_PACKAGE)) // Returned by RoResolveNamespace when using 3rd party WinRT types in classic process + { + if (fThrowOnFileNotFound) + { // Throw NotSupportedException (with custom message) wrapped by TypeLoadException to give user type name for diagnostics + // Note: TypeLoadException is equivalent of FileNotFound in WinRT world + EEMessageException ex(kNotSupportedException, IDS_EE_WINRT_THIRDPARTY_NOTSUPPORTED); + EX_THROW_WITH_INNER(EETypeLoadException, (pSpec->GetWinRtTypeNamespace(), pSpec->GetWinRtTypeClassName(), nullptr, nullptr, IDS_EE_WINRT_LOADFAILURE), &ex); + } + } + else if ((hr == CLR_E_BIND_UNRECOGNIZED_IDENTITY_FORMAT) || // Returned e.g. for WinRT type name without namespace + (hr == COR_E_PLATFORMNOTSUPPORTED)) // Using WinRT on pre-Win8 OS + { + if (fThrowOnFileNotFound) + { // Throw ArgumentException/PlatformNotSupportedException wrapped by TypeLoadException to give user type name for diagnostics + // Note: TypeLoadException is equivalent of FileNotFound in WinRT world + EEMessageException ex(hr); + EX_THROW_WITH_INNER(EETypeLoadException, (pSpec->GetWinRtTypeNamespace(), pSpec->GetWinRtTypeClassName(), nullptr, nullptr, IDS_EE_WINRT_LOADFAILURE), &ex); + } + } + else + { + IfFailThrow(hr); + } + } + _ASSERTE((FAILED(hr) && !fThrowOnFileNotFound) || pAssembly != nullptr); + + return pAssembly.Extract(); + } + else +#endif // FEATURE_COMINTEROP + if (pSpec->HasUniqueIdentity()) + { + HRESULT hrBindResult = S_OK; + PEAssemblyHolder result; + +#if defined(FEATURE_COMINTEROP) && defined(FEATURE_REFLECTION_ONLY_LOAD) + // We want to keep this holder around to avoid closing and remapping the file again - calls to Fusion further down will open the file again + ReleaseHolder pMetaDataAssemblyImport; + + // Special case ReflectionOnlyLoadFrom on .winmd (WinRT) assemblies + if (pSpec->IsIntrospectionOnly() && (pSpec->m_wszCodeBase != NULL)) + { // This is a LoadFrom request - we need to find out if it is .winmd file or classic managed assembly + HRESULT hr = S_OK; + + StackSString sPath(pSpec->GetCodeBase()); + PEAssembly::UrlToPath(sPath); + + // Open MetaData of the file + hr = GetAssemblyMDInternalImportEx( + sPath, + IID_IMetaDataAssemblyImport, + MDInternalImport_Default, + (IUnknown **)&pMetaDataAssemblyImport); + if (SUCCEEDED(hr)) + { + DWORD dwAssemblyFlags = 0; + hr = pMetaDataAssemblyImport->GetAssemblyProps( + TokenFromRid(1, mdtAssembly), + nullptr, // ppbPublicKey + nullptr, // pcbPublicKey + nullptr, // pulHashAlgId + nullptr, // szName + 0, // cchName + nullptr, // pchName + nullptr, // pMetaData + &dwAssemblyFlags); + if (SUCCEEDED(hr) && IsAfContentType_WindowsRuntime(dwAssemblyFlags)) + { // It is .winmd file + _ASSERTE(!AppX::IsAppXProcess()); + + ReleaseHolder pPrivAssembly; + ReleaseHolder pAssembly; + + hr = m_pReflectionOnlyWinRtBinder->BindAssemblyExplicit(sPath, &pPrivAssembly); + if (SUCCEEDED(hr)) + { + hr = BindHostedPrivAssembly(nullptr, pPrivAssembly, nullptr, &pAssembly, TRUE); + _ASSERTE(FAILED(hr) || (pAssembly != nullptr)); + } + if (FAILED(hr)) + { + if (fThrowOnFileNotFound) + { + ThrowHR(hr); + } + return nullptr; + } + return pAssembly.Extract(); + } + } + } +#endif //FEATURE_COMINTEROP && FEATURE_REFLECTION_ONLY_LOAD + + EX_TRY + { + if (!IsCached(pSpec)) + { + +#ifdef FEATURE_FUSION + if (fRaisePrebindEvents + && (result = TryResolveAssembly(pSpec, TRUE /*fPreBind*/)) != NULL + && result->CanUseWithBindingCache()) + { + // Failure to add simply means someone else beat us to it. In that case + // the FindCachedFile call below (after catch block) will update result + // to the cached value. + AddFileToCache(pSpec, result, TRUE /*fAllowFailure*/); + } + else +#endif + { + bool fAddFileToCache = false; + + BOOL fIsWellKnown = FALSE; + +#ifdef FEATURE_FUSION + SafeComHolderPreemp pIAssembly; + SafeComHolderPreemp pNativeFusionAssembly; + SafeComHolderPreemp pIHostAssembly; + SafeComHolderPreemp pFusionLog; + + // Event Tracing for Windows is used to log data for performance and functional testing purposes. + // The events below are used to measure the performance of assembly binding as a whole. + FireEtwBindingPhaseStart(GetId().m_dwId, ETWLoadContextNotAvailable, ETWFieldUnused, ETWLoaderLoadTypeNotAvailable, pSpec->m_wszCodeBase, NULL, GetClrInstanceId()); + fIsWellKnown = pSpec->FindAssemblyFile(this, + fThrowOnFileNotFound, + &pIAssembly, + &pIHostAssembly, + &pNativeFusionAssembly, + &pFusionLog, + &hrBindResult, + pCallerStackMark, + pLoadSecurity); + FireEtwBindingPhaseEnd(GetId().m_dwId, ETWLoadContextNotAvailable, ETWFieldUnused, ETWLoaderLoadTypeNotAvailable, pSpec->m_wszCodeBase, NULL, GetClrInstanceId()); + if (pIAssembly || pIHostAssembly) + { + + if (fIsWellKnown && + m_pRootAssembly && + pIAssembly == m_pRootAssembly->GetFusionAssembly()) + { + // This is a shortcut to avoid opening another copy of the process exe. + // In fact, we have other similar cases where we've called + // ExplicitBind() rather than normal binding, which aren't covered here. + + // @todo: It would be nice to populate the cache with those assemblies + // to avoid getting in this situation. + + result = m_pRootAssembly->GetManifestFile(); + result.SuppressRelease(); // Didn't get a refcount + } + else + { + BOOL isSystemAssembly = pSpec->IsMscorlib(); // can use SystemDomain::m_pSystemAssembly + BOOL isIntrospectionOnly = pSpec->IsIntrospectionOnly(); + if (pIAssembly) + result = PEAssembly::Open(pIAssembly, pNativeFusionAssembly, pFusionLog, + isSystemAssembly, isIntrospectionOnly); + else + result = PEAssembly::Open(pIHostAssembly, isSystemAssembly, + isIntrospectionOnly); + } + fAddFileToCache = true; + } + else if (!fIsWellKnown) + { + // Trigger the resolve event also for non-throw situation. + // However, this code path will behave as if the resolve handler has thrown, + // that is, not trigger an MDA. + _ASSERTE(fThrowOnFileNotFound == FALSE); + + AssemblySpec NewSpec(this); + AssemblySpec *pFailedSpec = NULL; + + fForceReThrow = TRUE; // Managed resolve event handler can throw + + // Purposly ignore return value + PostBindResolveAssembly(pSpec, &NewSpec, hrBindResult, &pFailedSpec); + } +#else //!FEATURE_FUSION + // Use CoreClr's fusion alternative + CoreBindResult bindResult; + + pSpec->Bind(this, fThrowOnFileNotFound, &bindResult, FALSE /* fNgenExplicitBind */, FALSE /* fExplicitBindToNativeImage */, pCallerStackMark); + hrBindResult = bindResult.GetHRBindResult(); + + if (bindResult.Found()) + { + if (SystemDomain::SystemFile() && bindResult.IsMscorlib()) + { + // Avoid rebinding to another copy of mscorlib + result = SystemDomain::SystemFile(); + result.SuppressRelease(); // Didn't get a refcount + } + else + { + // IsSystem on the PEFile should be false, even for mscorlib satellites + result = PEAssembly::Open(&bindResult, + FALSE, pSpec->IsIntrospectionOnly()); + } + fAddFileToCache = true; + +#if defined(FEATURE_CORECLR) + // Setup the reference to the binder, which performed the bind, into the AssemblySpec + ICLRPrivBinder* pBinder = result->GetBindingContext(); + _ASSERTE(pBinder != NULL); + pSpec->SetBindingContext(pBinder); +#endif // defined(FEATURE_CORECLR) + } + +#endif //!FEATURE_FUSION + + if (fAddFileToCache) + { + +#ifdef FEATURE_REFLECTION_ONLY_LOAD + // PERF: This doesn't scale... + if (pSpec->IsIntrospectionOnly() && (pSpec->GetCodeBase() != NULL)) + { + IAssemblyName * pIAssemblyName = result->GetFusionAssemblyName(); + + AppDomain::AssemblyIterator i = IterateAssembliesEx((AssemblyIterationFlags)( + kIncludeLoaded | kIncludeIntrospection)); + CollectibleAssemblyHolder pCachedDomainAssembly; + while (i.Next(pCachedDomainAssembly.This())) + { + IAssemblyName * pCachedAssemblyName = pCachedDomainAssembly->GetAssembly()->GetFusionAssemblyName(); + if (pCachedAssemblyName->IsEqual(pIAssemblyName, ASM_CMPF_IL_ALL) == S_OK) + { + if (!pCachedDomainAssembly->GetAssembly()->GetManifestModule()->GetFile()->Equals(result)) + { + COMPlusThrow(kFileLoadException, IDS_EE_REFLECTIONONLY_LOADFROM, pSpec->GetCodeBase()); + } + } + } + } +#endif //FEATURE_REFLECTION_ONLY_LOAD + + if (pSpec->CanUseWithBindingCache() && result->CanUseWithBindingCache()) + { + // Failure to add simply means someone else beat us to it. In that case + // the FindCachedFile call below (after catch block) will update result + // to the cached value. + AddFileToCache(pSpec, result, TRUE /*fAllowFailure*/); + } + } + else if (!fIsWellKnown) + { + // Trigger the resolve event also for non-throw situation. + // However, this code path will behave as if the resolve handler has thrown, + // that is, not trigger an MDA. + _ASSERTE(fThrowOnFileNotFound == FALSE); + + AssemblySpec NewSpec(this); + AssemblySpec *pFailedSpec = NULL; + + fForceReThrow = TRUE; // Managed resolve event handler can throw + + // Purposly ignore return value + PostBindResolveAssembly(pSpec, &NewSpec, hrBindResult, &pFailedSpec); + } + } + } + } + EX_CATCH + { + Exception *ex = GET_EXCEPTION(); + + AssemblySpec NewSpec(this); + AssemblySpec *pFailedSpec = NULL; + + // Let transient exceptions or managed resolve event handler exceptions propagate + if (ex->IsTransient() || fForceReThrow) + { + EX_RETHROW; + } + + { + // This is not executed for SO exceptions so we need to disable the backout + // stack validation to prevent false violations from being reported. + DISABLE_BACKOUT_STACK_VALIDATION; + + BOOL fFailure = PostBindResolveAssembly(pSpec, &NewSpec, ex->GetHR(), &pFailedSpec); + if (fFailure) + { + BOOL bFileNotFoundException = + (EEFileLoadException::GetFileLoadKind(ex->GetHR()) == kFileNotFoundException); + + if (!bFileNotFoundException) + { + fFailure = AddExceptionToCache(pFailedSpec, ex); + } // else, fFailure stays TRUE + // Effectively, fFailure == bFileNotFoundException || AddExceptionToCache(pFailedSpec, ex) + + // Only throw this exception if we are the first in the cache + if (fFailure) + { + // + // If the BindingFailure MDA is enabled, trigger one for this failure + // Note: TryResolveAssembly() can also throw if an AssemblyResolve event subscriber throws + // and the MDA isn't sent in this case (or for transient failure cases) + // +#ifdef MDA_SUPPORTED + MdaBindingFailure* pProbe = MDA_GET_ASSISTANT(BindingFailure); + if (pProbe) + { + // Transition to cooperative GC mode before using any OBJECTREFs. + GCX_COOP(); + + OBJECTREF exceptionObj = GET_THROWABLE(); + GCPROTECT_BEGIN(exceptionObj) + { + pProbe->BindFailed(pFailedSpec, &exceptionObj); + } + GCPROTECT_END(); + } +#endif + + // In the same cases as for the MDA, store the failure information for DAC to read + if (IsDebuggerAttached()) { + FailedAssembly *pFailed = new FailedAssembly(); + pFailed->Initialize(pFailedSpec, ex); + IfFailThrow(m_failedAssemblies.Append(pFailed)); + } + + if (!bFileNotFoundException || fThrowOnFileNotFound) + { + + // V1.1 App-compatibility workaround. See VSW530166 if you want to whine about it. + // + // In Everett, if we failed to download an assembly because of a broken network cable, + // we returned a FileNotFoundException with a COR_E_FILENOTFOUND hr embedded inside + // (which would be exposed when marshaled to native.) + // + // In Whidbey, we now set the more appropriate INET_E_RESOURCE_NOT_FOUND hr. But + // the online/offline switch code in VSTO for Everett hardcoded a check for + // COR_E_FILENOTFOUND. + // + // So now, to keep that code from breaking, we have to remap INET_E_RESOURCE_NOT_FOUND + // back to COR_E_FILENOTFOUND. We're doing it here rather down in Fusion so as to affect + // the least number of callers. + + if (ex->GetHR() == INET_E_RESOURCE_NOT_FOUND) + { + EEFileLoadException::Throw(pFailedSpec, COR_E_FILENOTFOUND, ex); + } + + if (EEFileLoadException::CheckType(ex)) + { + if (pFailedSpec == pSpec) + { + EX_RETHROW; //preserve the information + } + else + { + StackSString exceptionDisplayName, failedSpecDisplayName; + + ((EEFileLoadException*)ex)->GetName(exceptionDisplayName); + pFailedSpec->GetFileOrDisplayName(0, failedSpecDisplayName); + + if (exceptionDisplayName.CompareCaseInsensitive(failedSpecDisplayName) == 0) + { + EX_RETHROW; // Throw the original exception. Otherwise, we'd throw an exception that contains the same message twice. + } + } + } + + EEFileLoadException::Throw(pFailedSpec, ex->GetHR(), ex); + } + + } + } + } + } + EX_END_CATCH(RethrowTerminalExceptions); + + // Now, if it's a cacheable bind we need to re-fetch the result from the cache, as we may have been racing with another + // thread to store our result. Note that we may throw from here, if there is a cached exception. + // This will release the refcount of the current result holder (if any), and will replace + // it with a non-addref'ed result + if (pSpec->CanUseWithBindingCache() && (result== NULL || result->CanUseWithBindingCache())) + { + result = FindCachedFile(pSpec); + + if (result != NULL) + result->AddRef(); + } + + return result.Extract(); + } + else + { + // Unsupported content type + if (fThrowOnFileNotFound) + { + ThrowHR(COR_E_BADIMAGEFORMAT); + } + return nullptr; + } +} // AppDomain::BindAssemblySpec + + +#ifdef FEATURE_REFLECTION_ONLY_LOAD +DomainAssembly * +AppDomain::BindAssemblySpecForIntrospectionDependencies( + AssemblySpec * pSpec) +{ + STANDARD_VM_CONTRACT; + + PRECONDITION(CheckPointer(pSpec)); + PRECONDITION(pSpec->GetAppDomain() == this); + PRECONDITION(pSpec->IsIntrospectionOnly()); + PRECONDITION(this == ::GetAppDomain()); + + PEAssemblyHolder result; + HRESULT hr; + + if (!pSpec->HasUniqueIdentity()) + { + if (!pSpec->HasBindableIdentity()) + { + COMPlusThrowHR(E_UNEXPECTED); + } + + // In classic (non-AppX), this is initilized by AppDomain constructor + _ASSERTE(m_pReflectionOnlyWinRtBinder != NULL); + + ReleaseHolder pPrivAssembly; + hr = m_pReflectionOnlyWinRtBinder->BindWinRtType( + pSpec->GetWinRtTypeNamespace(), + pSpec->GetWinRtTypeClassName(), + pSpec->GetParentAssembly(), + &pPrivAssembly); + if (FAILED(hr)) + { + if (hr == CLR_E_BIND_TYPE_NOT_FOUND) + { // We could not find the type - throw TypeLoadException to give user type name for diagnostics + EX_THROW(EETypeLoadException, (pSpec->GetWinRtTypeNamespace(), pSpec->GetWinRtTypeClassName(), nullptr, nullptr, IDS_EE_REFLECTIONONLY_WINRT_LOADFAILURE)); + } + if (!Exception::IsTransient(hr)) + { // Throw the HRESULT as exception wrapped by TypeLoadException to give user type name for diagnostics + EEMessageException ex(hr); + EX_THROW_WITH_INNER(EETypeLoadException, (pSpec->GetWinRtTypeNamespace(), pSpec->GetWinRtTypeClassName(), nullptr, nullptr, IDS_EE_REFLECTIONONLY_WINRT_LOADFAILURE), &ex); + } + IfFailThrow(hr); + } + + IfFailThrow(BindHostedPrivAssembly(nullptr, pPrivAssembly, nullptr, &result, TRUE)); + _ASSERTE(result != nullptr); + return LoadDomainAssembly(pSpec, result, FILE_LOADED); + } + + EX_TRY + { + if (!IsCached(pSpec)) + { + result = TryResolveAssembly(pSpec, TRUE /*fPreBind*/); + if (result != NULL && result->CanUseWithBindingCache()) + { + // Failure to add simply means someone else beat us to it. In that case + // the FindCachedFile call below (after catch block) will update result + // to the cached value. + AddFileToCache(pSpec, result, TRUE /*fAllowFailure*/); + } + } + } + EX_CATCH + { + Exception *ex = GET_EXCEPTION(); + AssemblySpec NewSpec(this); + AssemblySpec *pFailedSpec = NULL; + + // Let transient exceptions propagate + if (ex->IsTransient()) + { + EX_RETHROW; + } + + // Non-"file not found" exception also propagate + BOOL fFailure = PostBindResolveAssembly(pSpec, &NewSpec, ex->GetHR(), &pFailedSpec); + if(fFailure) + { + if (AddExceptionToCache(pFailedSpec, ex)) + { + if ((pFailedSpec == pSpec) && EEFileLoadException::CheckType(ex)) + { + EX_RETHROW; //preserve the information + } + else + EEFileLoadException::Throw(pFailedSpec, ex->GetHR(), ex); + } + } + } + EX_END_CATCH(RethrowTerminalExceptions); + + result = FindCachedFile(pSpec); + result.SuppressRelease(); + + + if (result) + { + // It was either already in the spec cache or the prebind event returned a result. + return LoadDomainAssembly(pSpec, result, FILE_LOADED); + } + + + // Otherwise, look in the list of assemblies already loaded for reflectiononly. + IAssemblyName * ptmp = NULL; + hr = pSpec->CreateFusionName(&ptmp); + if (FAILED(hr)) + { + COMPlusThrowHR(hr); + } + SafeComHolder pIAssemblyName(ptmp); + + // Note: We do not support introspection-only collectible assemblies (yet) + AppDomain::AssemblyIterator i = IterateAssembliesEx((AssemblyIterationFlags)( + kIncludeLoaded | kIncludeIntrospection | kExcludeCollectible)); + CollectibleAssemblyHolder pCachedDomainAssembly; + + while (i.Next(pCachedDomainAssembly.This())) + { + _ASSERTE(!pCachedDomainAssembly->IsCollectible()); + IAssemblyName * pCachedAssemblyName = pCachedDomainAssembly->GetAssembly()->GetFusionAssemblyName(); + if (pCachedAssemblyName->IsEqual(pIAssemblyName, ASM_CMPF_IL_ALL) == S_OK) + { + return pCachedDomainAssembly; + } + } + // If not found in that list, it is an ERROR. Yes, this is by design. + StackSString name; + pSpec->GetFileOrDisplayName(0, name); + COMPlusThrow(kFileLoadException, IDS_EE_REFLECTIONONLY_LOADFAILURE,name); +} // AppDomain::BindAssemblySpecForIntrospectionDependencies +#endif // FEATURE_REFLECTION_ONLY_LOAD + +PEAssembly *AppDomain::TryResolveAssembly(AssemblySpec *pSpec, BOOL fPreBind) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_ANY; + + PEAssembly *result = NULL; + + EX_TRY + { + result = pSpec->ResolveAssemblyFile(this, fPreBind); + } + EX_HOOK + { + Exception *pEx = GET_EXCEPTION(); + + if (!pEx->IsTransient()) + { + AddExceptionToCache(pSpec, pEx); + if (!EEFileLoadException::CheckType(pEx)) + EEFileLoadException::Throw(pSpec, pEx->GetHR(), pEx); + } + } + EX_END_HOOK; + + return result; +} + +#ifdef FEATURE_FUSION +void AppDomain::GetFileFromFusion(IAssembly *pIAssembly, LPCWSTR wszModuleName, + SString &path) +{ + CONTRACTL + { + INSTANCE_CHECK; + THROWS; + INJECT_FAULT(COMPlusThrowOM()); + } + CONTRACTL_END; + + SafeComHolder pImport; + IfFailThrow(pIAssembly->GetModuleByName(wszModuleName, &pImport)); + + if (!pImport->IsAvailable()) { + AssemblySink* pSink = AllocateAssemblySink(NULL); + SafeComHolder sinkholder(pSink); + SafeComHolder pResult; + + IfFailThrow(FusionBind::RemoteLoadModule(GetFusionContext(), + pImport, + pSink, + &pResult)); + pResult->AddRef(); + pImport.Assign(pResult); + } + + DWORD dwPath = 0; + pImport->GetModulePath(NULL, &dwPath); + + LPWSTR buffer = path.OpenUnicodeBuffer(dwPath-1); + IfFailThrow(pImport->GetModulePath(buffer, &dwPath)); + path.CloseBuffer(); +} + +PEAssembly *AppDomain::BindExplicitAssembly(HMODULE hMod, BOOL bindable) +{ + CONTRACT(PEAssembly *) + { + PRECONDITION(CheckPointer(hMod)); + GC_TRIGGERS; + THROWS; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACT_END; + + SafeComHolder pFusionAssembly; + SafeComHolder pNativeFusionAssembly; + SafeComHolder pFusionLog; + + StackSString path; + PEImage::GetPathFromDll(hMod, path); + + HRESULT hr = ExplicitBind(path, GetFusionContext(), + bindable ? EXPLICITBIND_FLAGS_EXE : EXPLICITBIND_FLAGS_NON_BINDABLE, + NULL, &pFusionAssembly, &pNativeFusionAssembly,&pFusionLog); + if (FAILED(hr)) + EEFileLoadException::Throw(path, hr); + + RETURN PEAssembly::OpenHMODULE(hMod, pFusionAssembly,pNativeFusionAssembly, pFusionLog, FALSE); +} + +Assembly *AppDomain::LoadExplicitAssembly(HMODULE hMod, BOOL bindable) +{ + CONTRACT(Assembly *) + { + PRECONDITION(CheckPointer(hMod)); + GC_TRIGGERS; + THROWS; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACT_END; + + PEAssemblyHolder pFile(BindExplicitAssembly(hMod, bindable)); + + RETURN LoadAssembly(NULL, pFile, FILE_ACTIVE); +} +#endif // FEATURE_FUSION + +ULONG AppDomain::AddRef() +{ + LIMITED_METHOD_CONTRACT; + return InterlockedIncrement(&m_cRef); +} + +ULONG AppDomain::Release() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(m_cRef > 0); + } + CONTRACTL_END; + + ULONG cRef = InterlockedDecrement(&m_cRef); + if (!cRef) + { + _ASSERTE (m_Stage == STAGE_CREATING || m_Stage == STAGE_CLOSED); + ADID adid=GetId(); + delete this; + TESTHOOKCALL(AppDomainDestroyed(adid.m_dwId)); + } + return (cRef); +} + +#ifdef FEATURE_FUSION +AssemblySink* AppDomain::AllocateAssemblySink(AssemblySpec* pSpec) +{ + CONTRACT(AssemblySink *) + { + THROWS; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACT_END; + + AssemblySink* ret = FastInterlockExchangePointer(&m_pAsyncPool, NULL); + + if(ret == NULL) + ret = new AssemblySink(this); + else + ret->AddRef(); + ret->SetAssemblySpec(pSpec); + RETURN ret; +} +#endif + +AppDomain* AppDomain::s_pAppDomainToRaiseUnloadEvent; +BOOL AppDomain::s_fProcessUnloadDomainEvent = FALSE; + +#ifndef CROSSGEN_COMPILE + +void AppDomain::RaiseUnloadDomainEvent_Wrapper(LPVOID ptr) +{ + CONTRACTL + { + THROWS; + MODE_COOPERATIVE; + GC_TRIGGERS; + INJECT_FAULT(COMPlusThrowOM();); + SO_INTOLERANT; + } + CONTRACTL_END; + + AppDomain* pDomain = (AppDomain *) ptr; + pDomain->RaiseUnloadDomainEvent(); +} + +void AppDomain::ProcessUnloadDomainEventOnFinalizeThread() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + Thread *pThread = GetThread(); + _ASSERTE (pThread && IsFinalizerThread()); + + // if we are not unloading domain now, do not process the event + if (SystemDomain::AppDomainBeingUnloaded() == NULL) + { + s_pAppDomainToRaiseUnloadEvent->SetStage(STAGE_UNLOAD_REQUESTED); + s_pAppDomainToRaiseUnloadEvent->EnableADUnloadWorker( + s_pAppDomainToRaiseUnloadEvent->IsRudeUnload()?EEPolicy::ADU_Rude:EEPolicy::ADU_Safe); + FastInterlockExchangePointer(&s_pAppDomainToRaiseUnloadEvent, NULL); + return; + } + FastInterlockExchange((LONG*)&s_fProcessUnloadDomainEvent, TRUE); + AppDomain::EnableADUnloadWorkerForFinalizer(); + pThread->SetThreadStateNC(Thread::TSNC_RaiseUnloadEvent); + s_pAppDomainToRaiseUnloadEvent->RaiseUnloadDomainEvent(); + pThread->ResetThreadStateNC(Thread::TSNC_RaiseUnloadEvent); + s_pAppDomainToRaiseUnloadEvent->EnableADUnloadWorker( + s_pAppDomainToRaiseUnloadEvent->IsRudeUnload()?EEPolicy::ADU_Rude:EEPolicy::ADU_Safe); + FastInterlockExchangePointer(&s_pAppDomainToRaiseUnloadEvent, NULL); + FastInterlockExchange((LONG*)&s_fProcessUnloadDomainEvent, FALSE); + + if (pThread->IsAbortRequested()) + { + pThread->UnmarkThreadForAbort(Thread::TAR_Thread); + } +} + +void AppDomain::RaiseUnloadDomainEvent() +{ + CONTRACTL + { + NOTHROW; + MODE_COOPERATIVE; + GC_TRIGGERS; + SO_INTOLERANT; + } + CONTRACTL_END; + + EX_TRY + { + Thread *pThread = GetThread(); + if (this != pThread->GetDomain()) + { + pThread->DoADCallBack(this, AppDomain::RaiseUnloadDomainEvent_Wrapper, this,ADV_FINALIZER|ADV_COMPILATION); + } + else + { + struct _gc + { + APPDOMAINREF Domain; + OBJECTREF Delegate; + } gc; + ZeroMemory(&gc, sizeof(gc)); + + GCPROTECT_BEGIN(gc); + gc.Domain = (APPDOMAINREF) GetRawExposedObject(); + if (gc.Domain != NULL) + { + gc.Delegate = gc.Domain->m_pDomainUnloadEventHandler; + if (gc.Delegate != NULL) + DistributeEventReliably(&gc.Delegate, (OBJECTREF *) &gc.Domain); + } + GCPROTECT_END(); + } + } + EX_CATCH + { + //@TODO call a MDA here + } + EX_END_CATCH(SwallowAllExceptions); +} + +void AppDomain::RaiseLoadingAssemblyEvent(DomainAssembly *pAssembly) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + PRECONDITION(this == GetAppDomain()); + MODE_ANY; + } + CONTRACTL_END; + + GCX_COOP(); + FAULT_NOT_FATAL(); + OVERRIDE_TYPE_LOAD_LEVEL_LIMIT(CLASS_LOADED); + + EX_TRY + { + struct _gc { + APPDOMAINREF AppDomainRef; + OBJECTREF orThis; + } gc; + ZeroMemory(&gc, sizeof(gc)); + + if ((gc.AppDomainRef = (APPDOMAINREF) GetRawExposedObject()) != NULL) { + if (gc.AppDomainRef->m_pAssemblyEventHandler != NULL) + { + ARG_SLOT args[2]; + GCPROTECT_BEGIN(gc); + + gc.orThis = pAssembly->GetExposedAssemblyObject(); + + MethodDescCallSite onAssemblyLoad(METHOD__APP_DOMAIN__ON_ASSEMBLY_LOAD, &gc.orThis); + + // GetExposedAssemblyObject may cause a gc, so call this before filling args[0] + args[1] = ObjToArgSlot(gc.orThis); + args[0] = ObjToArgSlot(gc.AppDomainRef); + + onAssemblyLoad.Call(args); + + GCPROTECT_END(); + } + } + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions); +} + + +BOOL AppDomain::OnUnhandledException(OBJECTREF *pThrowable, BOOL isTerminating/*=TRUE*/) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_ANY; + + BOOL retVal= FALSE; + + GCX_COOP(); + + // The Everett behavior was to send the unhandled exception event only to the Default + // AppDomain (since that's the only place that exceptions actually went unhandled). + // + // During Whidbey development, we broadcast the event to all AppDomains in the process. + // + // But the official shipping Whidbey behavior is that the unhandled exception event is + // sent to the Default AppDomain and to whatever AppDomain the exception went unhandled + // in. To achieve this, we declare the exception to be unhandled *BEFORE* we marshal + // it back to the Default AppDomain at the base of the Finalizer, threadpool and managed + // threads. + // + // The rationale for sending the event to the Default AppDomain as well as the one the + // exception went unhandled in is: + // + // 1) This is compatible with the pre-Whidbey behavior, where only the Default AppDomain + // received the notification. + // + // 2) This is convenient for hosts, which don't want to bother injecting listeners into + // every single AppDomain. + + AppDomain *pAppDomain = GetAppDomain(); + OBJECTREF orSender = 0; + + GCPROTECT_BEGIN(orSender); + + orSender = pAppDomain->GetRawExposedObject(); + + retVal = pAppDomain->RaiseUnhandledExceptionEventNoThrow(&orSender, pThrowable, isTerminating); +#ifndef FEATURE_CORECLR +// CoreCLR#520: +// To make this work correctly we need the changes for coreclr 473 + if (pAppDomain != SystemDomain::System()->DefaultDomain()) + retVal |= SystemDomain::System()->DefaultDomain()->RaiseUnhandledExceptionEventNoThrow + (&orSender, pThrowable, isTerminating); +#endif + + GCPROTECT_END(); + + return retVal; +} + + +// Move outside of the AppDomain iteration, to avoid issues with the GC Frames being outside +// the domain transition. This is a chronic issue that causes us to report roots for an AppDomain +// after we have left it. This causes problems with AppDomain unloading that we only find +// with stress coverage.. +void AppDomain::RaiseOneExitProcessEvent() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + struct _gc + { + APPDOMAINREF Domain; + OBJECTREF Delegate; + } gc; + ZeroMemory(&gc, sizeof(gc)); + + EX_TRY { + + GCPROTECT_BEGIN(gc); + gc.Domain = (APPDOMAINREF) SystemDomain::GetCurrentDomain()->GetRawExposedObject(); + if (gc.Domain != NULL) + { + gc.Delegate = gc.Domain->m_pProcessExitEventHandler; + if (gc.Delegate != NULL) + DistributeEventReliably(&gc.Delegate, (OBJECTREF *) &gc.Domain); + } + GCPROTECT_END(); + + } EX_CATCH { + } EX_END_CATCH(SwallowAllExceptions); +} + +// Local wrapper used in AppDomain::RaiseExitProcessEvent, +// introduced solely to avoid stack overflow because of _alloca in the loop. +// It's just factored out body of the loop, but it has to be a member method of AppDomain, +// because it calls private RaiseOneExitProcessEvent +/*static*/ void AppDomain::RaiseOneExitProcessEvent_Wrapper(AppDomainIterator* pi) +{ + + STATIC_CONTRACT_MODE_COOPERATIVE; + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_TRIGGERS; + + EX_TRY { + ENTER_DOMAIN_PTR(pi->GetDomain(),ADV_ITERATOR) + AppDomain::RaiseOneExitProcessEvent(); + END_DOMAIN_TRANSITION; + } EX_CATCH { + } EX_END_CATCH(SwallowAllExceptions); +} + +static LONG s_ProcessedExitProcessEventCount = 0; + +LONG GetProcessedExitProcessEventCount() +{ + LIMITED_METHOD_CONTRACT; + return s_ProcessedExitProcessEventCount; +} + +void AppDomain::RaiseExitProcessEvent() +{ + if (!g_fEEStarted) + return; + + STATIC_CONTRACT_MODE_COOPERATIVE; + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_TRIGGERS; + + // Only finalizer thread during shutdown can call this function. + _ASSERTE ((g_fEEShutDown&ShutDown_Finalize1) && GetThread() == FinalizerThread::GetFinalizerThread()); + + _ASSERTE (GetThread()->PreemptiveGCDisabled()); + + _ASSERTE (GetThread()->GetDomain()->IsDefaultDomain()); + + AppDomainIterator i(TRUE); + while (i.Next()) + { + RaiseOneExitProcessEvent_Wrapper(&i); + FastInterlockIncrement(&s_ProcessedExitProcessEventCount); + } +} + +#ifndef FEATURE_CORECLR +void AppDomain::RaiseUnhandledExceptionEvent_Wrapper(LPVOID ptr) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + INJECT_FAULT(COMPlusThrowOM();); + SO_INTOLERANT; + } + CONTRACTL_END; + AppDomain::RaiseUnhandled_Args *args = (AppDomain::RaiseUnhandled_Args *) ptr; + + struct _gc { + OBJECTREF orThrowable; + OBJECTREF orSender; + } gc; + + ZeroMemory(&gc, sizeof(gc)); + + _ASSERTE(args->pTargetDomain == GetAppDomain()); + GCPROTECT_BEGIN(gc); + EX_TRY + { + SetObjectReference(&gc.orThrowable, + AppDomainHelper::CrossContextCopyFrom(args->pExceptionDomain, + args->pThrowable), + args->pTargetDomain); + + SetObjectReference(&gc.orSender, + AppDomainHelper::CrossContextCopyFrom(args->pExceptionDomain, + args->pSender), + args->pTargetDomain); + } + EX_CATCH + { + SetObjectReference(&gc.orThrowable, GET_THROWABLE(), args->pTargetDomain); + SetObjectReference(&gc.orSender, GetAppDomain()->GetRawExposedObject(), args->pTargetDomain); + } + EX_END_CATCH(SwallowAllExceptions) + *(args->pResult) = args->pTargetDomain->RaiseUnhandledExceptionEvent(&gc.orSender, + &gc.orThrowable, + args->isTerminating); + GCPROTECT_END(); + +} +#endif //!FEATURE_CORECLR + +BOOL +AppDomain::RaiseUnhandledExceptionEventNoThrow(OBJECTREF *pSender, OBJECTREF *pThrowable, BOOL isTerminating) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + BOOL bRetVal=FALSE; + + EX_TRY + { + bRetVal = RaiseUnhandledExceptionEvent(pSender, pThrowable, isTerminating); + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions) // Swallow any errors. + return bRetVal; + +} + +BOOL +AppDomain::HasUnhandledExceptionEventHandler() +{ + CONTRACTL + { + MODE_COOPERATIVE; + GC_NOTRIGGER; //essential + NOTHROW; + } + CONTRACTL_END; + if (!CanThreadEnter(GetThread())) + return FALSE; + if (GetRawExposedObject()==NULL) + return FALSE; + return (((APPDOMAINREF)GetRawExposedObject())->m_pUnhandledExceptionEventHandler!=NULL); +} + +BOOL +AppDomain::RaiseUnhandledExceptionEvent(OBJECTREF *pSender, OBJECTREF *pThrowable, BOOL isTerminating) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + if (!HasUnhandledExceptionEventHandler()) + return FALSE; + + BOOL result = FALSE; + + _ASSERTE(pThrowable != NULL && IsProtectedByGCFrame(pThrowable)); + _ASSERTE(pSender != NULL && IsProtectedByGCFrame(pSender)); + +#ifndef FEATURE_CORECLR + Thread *pThread = GetThread(); + if (this != pThread->GetDomain()) + { + RaiseUnhandled_Args args = {pThread->GetDomain(), this, pSender, pThrowable, isTerminating, &result}; + // call through DoCallBack with a domain transition + pThread->DoADCallBack(this, AppDomain::RaiseUnhandledExceptionEvent_Wrapper, &args, ADV_DEFAULTAD); + return result; + } +#else + _ASSERTE(this == GetThread()->GetDomain()); +#endif + + + OBJECTREF orDelegate = NULL; + + GCPROTECT_BEGIN(orDelegate); + + APPDOMAINREF orAD = (APPDOMAINREF) GetAppDomain()->GetRawExposedObject(); + + if (orAD != NULL) + { + orDelegate = orAD->m_pUnhandledExceptionEventHandler; + if (orDelegate != NULL) + { + result = TRUE; + DistributeUnhandledExceptionReliably(&orDelegate, pSender, pThrowable, isTerminating); + } + } + GCPROTECT_END(); + return result; +} + + +#ifndef FEATURE_CORECLR +// Create a domain based on a string name +AppDomain* AppDomain::CreateDomainContext(LPCWSTR fileName) +{ + CONTRACTL + { + THROWS; + MODE_COOPERATIVE; + GC_TRIGGERS; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + if(fileName == NULL) return NULL; + + AppDomain* pDomain = NULL; + + MethodDescCallSite valCreateDomain(METHOD__APP_DOMAIN__VAL_CREATE_DOMAIN); + + STRINGREF pFilePath = NULL; + GCPROTECT_BEGIN(pFilePath); + pFilePath = StringObject::NewString(fileName); + + ARG_SLOT args[1] = + { + ObjToArgSlot(pFilePath), + }; + + APPDOMAINREF pDom = (APPDOMAINREF) valCreateDomain.Call_RetOBJECTREF(args); + if(pDom != NULL) + { + Context* pContext = Context::GetExecutionContext(pDom); + if(pContext) + { + pDomain = pContext->GetDomain(); + } + } + GCPROTECT_END(); + + return pDomain; +} +#endif // !FEATURE_CORECLR + +#endif // CROSSGEN_COMPILE + +// You must be in the correct context before calling this +// routine. Therefore, it is only good for initializing the +// default domain. +void AppDomain::InitializeDomainContext(BOOL allowRedirects, + LPCWSTR pwszPath, + LPCWSTR pwszConfig) +{ + CONTRACTL + { + MODE_COOPERATIVE; + GC_TRIGGERS; + THROWS; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + if (NingenEnabled()) + { +#ifdef FEATURE_FUSION + CreateFusionContext(); +#endif // FEATURE_FUSION + +#ifdef FEATURE_VERSIONING + CreateFusionContext(); +#endif // FEATURE_VERSIONING + + return; + } + +#ifndef CROSSGEN_COMPILE + struct _gc { + STRINGREF pFilePath; + STRINGREF pConfig; + OBJECTREF ref; + PTRARRAYREF propertyNames; + PTRARRAYREF propertyValues; + } gc; + ZeroMemory(&gc, sizeof(gc)); + + GCPROTECT_BEGIN(gc); + if(pwszPath) + { + gc.pFilePath = StringObject::NewString(pwszPath); + } + + if(pwszConfig) + { + gc.pConfig = StringObject::NewString(pwszConfig); + } + +#ifndef FEATURE_CORECLR + StringArrayList *pPropertyNames; + StringArrayList *pPropertyValues; + CorHost2::GetDefaultAppDomainProperties(&pPropertyNames, &pPropertyValues); + + _ASSERTE(pPropertyNames->GetCount() == pPropertyValues->GetCount()); + + if (pPropertyNames->GetCount() > 0) + { + gc.propertyNames = (PTRARRAYREF)AllocateObjectArray(pPropertyNames->GetCount(), g_pStringClass); + gc.propertyValues = (PTRARRAYREF)AllocateObjectArray(pPropertyValues->GetCount(), g_pStringClass); + + for (DWORD i = 0; i < pPropertyNames->GetCount(); ++i) + { + STRINGREF propertyName = StringObject::NewString(pPropertyNames->Get(i)); + gc.propertyNames->SetAt(i, propertyName); + + STRINGREF propertyValue = StringObject::NewString(pPropertyValues->Get(i)); + gc.propertyValues->SetAt(i, propertyValue); + } + } +#endif // !FEATURE_CORECLR + + if ((gc.ref = GetExposedObject()) != NULL) + { + MethodDescCallSite setupDomain(METHOD__APP_DOMAIN__SETUP_DOMAIN); + + ARG_SLOT args[] = + { + ObjToArgSlot(gc.ref), + BoolToArgSlot(allowRedirects), + ObjToArgSlot(gc.pFilePath), + ObjToArgSlot(gc.pConfig), + ObjToArgSlot(gc.propertyNames), + ObjToArgSlot(gc.propertyValues) + }; + setupDomain.Call(args); + } + GCPROTECT_END(); + + CacheStringsForDAC(); +#endif // CROSSGEN_COMPILE +} + +#ifdef FEATURE_FUSION + +void AppDomain::SetupLoaderOptimization(DWORD optimization) +{ + STANDARD_VM_CONTRACT; + + GCX_COOP(); + + if ((GetExposedObject()) != NULL) + { + MethodDescCallSite setupLoaderOptimization(METHOD__APP_DOMAIN__SETUP_LOADER_OPTIMIZATION); + + ARG_SLOT args[2] = + { + ObjToArgSlot(GetExposedObject()), + optimization + }; + setupLoaderOptimization.Call(args); + } +} + +// The fusion context should only be null when appdomain is being setup +// and there should be no reason to protect the creation. +IApplicationContext *AppDomain::CreateFusionContext() +{ + CONTRACT(IApplicationContext *) + { + GC_TRIGGERS; + THROWS; + MODE_ANY; + POSTCONDITION(CheckPointer(RETVAL)); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACT_END; + + if (m_pFusionContext == NULL) + { + ETWOnStartup (FusionAppCtx_V1, FusionAppCtxEnd_V1); + + GCX_PREEMP(); + + SafeComHolderPreemp pFusionContext; + + IfFailThrow(FusionBind::CreateFusionContext(NULL, &pFusionContext)); + +#if defined(FEATURE_COMINTEROP) && !defined(FEATURE_CORECLR) + CLRPrivBinderWinRT * pWinRtBinder; + if (AppX::IsAppXProcess()) + { // Note: Fusion binder is used in AppX to bind .NET Fx assemblies - some of them depend on .winmd files (e.g. System.Runtime.WindowsRuntime.dll) + CLRPrivBinderAppX * pAppXBinder = CLRPrivBinderAppX::GetOrCreateBinder(); + pWinRtBinder = pAppXBinder->GetWinRtBinder(); + } + else + { + pWinRtBinder = m_pWinRtBinder; + } + _ASSERTE(pWinRtBinder != nullptr); + + IfFailThrow(SetApplicationContext_WinRTBinder( + pFusionContext, + static_cast(pWinRtBinder))); +#endif + +#ifdef FEATURE_PREJIT + if (NGENImagesAllowed()) + { + // Set the native image settings so fusion will bind native images + SString zapString(g_pConfig->ZapSet()); + FusionBind::SetApplicationContextStringProperty(pFusionContext, ACTAG_ZAP_STRING, zapString); + FusionBind::SetApplicationContextDWORDProperty(pFusionContext, ACTAG_ZAP_CONFIG_FLAGS, + PEFile::GetNativeImageConfigFlags()); + } +#endif // FEATURE_PREJIT + + pFusionContext.SuppressRelease(); + m_pFusionContext = pFusionContext; + + DWORD dwId = m_dwId.m_dwId; + IfFailThrow(m_pFusionContext->Set(ACTAG_APP_DOMAIN_ID, &dwId, sizeof(DWORD), 0)); + + if (HasLoadContextHostBinder()) + FusionBind::SetApplicationContextDWORDProperty(pFusionContext, ACTAG_FX_ONLY,1); + + } + + RETURN m_pFusionContext; +} + +void AppDomain::TurnOnBindingRedirects() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + + if ((GetExposedObject()) != NULL) + { + MethodDescCallSite turnOnBindingRedirects(METHOD__APP_DOMAIN__TURN_ON_BINDING_REDIRECTS); + ARG_SLOT args[1] = + { + ObjToArgSlot(GetExposedObject()), + }; + turnOnBindingRedirects.Call(args); + } + + IfFailThrow(m_pFusionContext->Set(ACTAG_DISALLOW_APP_BINDING_REDIRECTS, + NULL, + 0, + 0)); +} + +void AppDomain::SetupExecutableFusionContext(LPCWSTR exePath) +{ + CONTRACTL + { + STANDARD_VM_CHECK; + PRECONDITION(GetAppDomain() == this); + } + CONTRACTL_END; + + GCX_COOP(); + + struct _gc { + STRINGREF pFilePath; + OBJECTREF ref; + } gc; + ZeroMemory(&gc, sizeof(gc)); + + GCPROTECT_BEGIN(gc); + gc.pFilePath = StringObject::NewString(exePath); + + if ((gc.ref = GetExposedObject()) != NULL) + { + MethodDescCallSite setDomainContext(METHOD__APP_DOMAIN__SET_DOMAIN_CONTEXT, &gc.ref); + ARG_SLOT args[2] = + { + ObjToArgSlot(gc.ref), + ObjToArgSlot(gc.pFilePath), + }; + setDomainContext.Call(args); + } + + GCPROTECT_END(); + +} + +BOOL AppDomain::SetContextProperty(IApplicationContext* pFusionContext, + LPCWSTR pProperty, OBJECTREF* obj) + +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + if (GetAppDomain()->HasLoadContextHostBinder()) + COMPlusThrow(kNotSupportedException); + + + if(obj) { + if ((*obj) != NULL){ + MethodTable* pMT = (*obj)->GetMethodTable(); + DWORD lgth; + + if(MscorlibBinder::IsClass(pMT, CLASS__STRING)) { + + lgth = (ObjectToSTRINGREF(*(StringObject**)obj))->GetStringLength(); + CQuickBytes qb; + LPWSTR wszValue = (LPWSTR) qb.AllocThrows((lgth+1)*sizeof(WCHAR)); + memcpy(wszValue, (ObjectToSTRINGREF(*(StringObject**)obj))->GetBuffer(), lgth*sizeof(WCHAR)); + if(lgth > 0 && wszValue[lgth-1] == '/') + lgth--; + wszValue[lgth] = W('\0'); + + LOG((LF_LOADER, + LL_INFO10, + "Set: %S: *%S*.\n", + pProperty, wszValue)); + + IfFailThrow(pFusionContext->Set(pProperty, + wszValue, + (lgth+1) * sizeof(WCHAR), + 0)); + } + else { + // Pin byte array for loading + Wrapper handle( + GetAppDomain()->CreatePinningHandle(*obj)); + + const BYTE *pbArray = ((U1ARRAYREF)(*obj))->GetDirectConstPointerToNonObjectElements(); + DWORD cbArray = (*obj)->GetNumComponents(); + + IfFailThrow(pFusionContext->Set(pProperty, + (LPVOID) pbArray, + cbArray, + 0)); + } + } + else { // Un-set the property + IfFailThrow(pFusionContext->Set(pProperty, + NULL, + 0, + 0)); + } + } + + return TRUE; +} +#endif // FEATURE_FUSION + +#ifdef FEATURE_VERSIONING +IUnknown *AppDomain::CreateFusionContext() +{ + CONTRACT(IUnknown *) + { + GC_TRIGGERS; + THROWS; + MODE_ANY; + POSTCONDITION(CheckPointer(RETVAL)); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACT_END; + + if (!m_pFusionContext) + { + ETWOnStartup (FusionAppCtx_V1, FusionAppCtxEnd_V1); + CLRPrivBinderCoreCLR *pTPABinder = NULL; + + GCX_PREEMP(); + + // Initialize the assembly binder for the default context loads for CoreCLR. + IfFailThrow(CCoreCLRBinderHelper::DefaultBinderSetupContext(GetId().m_dwId, &pTPABinder)); + m_pFusionContext = reinterpret_cast(pTPABinder); + +#if defined(FEATURE_HOST_ASSEMBLY_RESOLVER) + // By default, initial binding context setup for CoreCLR is also the TPABinding context + (m_pTPABinderContext = pTPABinder)->AddRef(); +#endif // defined(FEATURE_HOST_ASSEMBLY_RESOLVER) + + } + + RETURN m_pFusionContext; +} +#endif // FEATURE_VERSIONING + +#ifdef FEATURE_FUSION +LPWSTR AppDomain::GetDynamicDir() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + if (m_pwDynamicDir == NULL) { + + BaseDomain::LockHolder lh(this); + + if(m_pwDynamicDir == NULL) { + IApplicationContext* pFusionContext = GetFusionContext(); + _ASSERTE(pFusionContext); + + HRESULT hr = S_OK; + DWORD dwSize = 0; + hr = pFusionContext->GetDynamicDirectory(NULL, &dwSize); + AllocMemHolder tempDynamicDir; + + if(hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) { + tempDynamicDir = GetLowFrequencyHeap()->AllocMem(S_SIZE_T(dwSize) * S_SIZE_T(sizeof(WCHAR))); + hr = pFusionContext->GetDynamicDirectory(tempDynamicDir, &dwSize); + } + if(hr==HRESULT_FROM_WIN32(ERROR_NOT_FOUND)) + return NULL; + IfFailThrow(hr); + + tempDynamicDir.SuppressRelease(); + m_pwDynamicDir = tempDynamicDir; + } + // lh out of scope here + } + + return m_pwDynamicDir;; +} +#endif //FEATURE_FUSION + + +//--------------------------------------------------------------------------------------- +// +// AppDomain::IsDebuggerAttached - is a debugger attached to this process +// +// Arguments: +// None +// +// Return Value: +// TRUE if a debugger is attached to this process, FALSE otherwise. +// +// Notes: +// This is identical to CORDebuggerAttached. This exists idependantly for legacy reasons - we used to +// support attaching to individual AppDomains. This should probably go away eventually. +// + +BOOL AppDomain::IsDebuggerAttached() +{ + LIMITED_METHOD_CONTRACT; + + if (CORDebuggerAttached()) + { + return TRUE; + } + else + { + return FALSE; + } +} + +#ifdef DEBUGGING_SUPPORTED + +// This is called from the debugger to request notification events from +// Assemblies, Modules, Types in this appdomain. +BOOL AppDomain::NotifyDebuggerLoad(int flags, BOOL attaching) +{ + WRAPPER_NO_CONTRACT; + BOOL result = FALSE; + + if (!attaching && !IsDebuggerAttached()) + return FALSE; + + AssemblyIterator i; + + // Attach to our assemblies + LOG((LF_CORDB, LL_INFO100, "AD::NDA: Iterating assemblies\n")); + i = IterateAssembliesEx((AssemblyIterationFlags)(kIncludeLoaded | kIncludeLoading | kIncludeExecution)); + CollectibleAssemblyHolder pDomainAssembly; + while (i.Next(pDomainAssembly.This())) + { + result = (pDomainAssembly->NotifyDebuggerLoad(flags, attaching) || + result); + } + + return result; +} + +void AppDomain::NotifyDebuggerUnload() +{ + WRAPPER_NO_CONTRACT; + if (!IsDebuggerAttached()) + return; + + LOG((LF_CORDB, LL_INFO10, "AD::NDD domain [%d] %#08x %ls\n", + GetId().m_dwId, this, GetFriendlyNameForLogging())); + + LOG((LF_CORDB, LL_INFO100, "AD::NDD: Interating domain bound assemblies\n")); + AssemblyIterator i = IterateAssembliesEx((AssemblyIterationFlags)(kIncludeLoaded | kIncludeLoading | kIncludeExecution)); + CollectibleAssemblyHolder pDomainAssembly; + + // Detach from our assemblies + while (i.Next(pDomainAssembly.This())) + { + LOG((LF_CORDB, LL_INFO100, "AD::NDD: Iterating assemblies\n")); + pDomainAssembly->NotifyDebuggerUnload(); + } +} +#endif // DEBUGGING_SUPPORTED + +void AppDomain::SetSystemAssemblyLoadEventSent(BOOL fFlag) +{ + LIMITED_METHOD_CONTRACT; + if (fFlag == TRUE) + m_dwFlags |= LOAD_SYSTEM_ASSEMBLY_EVENT_SENT; + else + m_dwFlags &= ~LOAD_SYSTEM_ASSEMBLY_EVENT_SENT; +} + +BOOL AppDomain::WasSystemAssemblyLoadEventSent(void) +{ + LIMITED_METHOD_CONTRACT; + return ((m_dwFlags & LOAD_SYSTEM_ASSEMBLY_EVENT_SENT) == 0) ? FALSE : TRUE; +} + +#ifndef CROSSGEN_COMPILE +// U->M thunks created in this domain and not associated with a delegate. +UMEntryThunkCache *AppDomain::GetUMEntryThunkCache() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + if (!m_pUMEntryThunkCache) + { + UMEntryThunkCache *pUMEntryThunkCache = new UMEntryThunkCache(this); + + if (FastInterlockCompareExchangePointer(&m_pUMEntryThunkCache, pUMEntryThunkCache, NULL) != NULL) + { + // some thread swooped in and set the field + delete pUMEntryThunkCache; + } + } + _ASSERTE(m_pUMEntryThunkCache); + return m_pUMEntryThunkCache; +} + +#ifdef FEATURE_COMINTEROP + +ComCallWrapperCache *AppDomain::GetComCallWrapperCache() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + if (! m_pComCallWrapperCache) + { + BaseDomain::LockHolder lh(this); + + if (! m_pComCallWrapperCache) + m_pComCallWrapperCache = ComCallWrapperCache::Create(this); + } + _ASSERTE(m_pComCallWrapperCache); + return m_pComCallWrapperCache; +} + +RCWRefCache *AppDomain::GetRCWRefCache() +{ + CONTRACT(RCWRefCache*) + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + POSTCONDITION(CheckPointer(RETVAL)); + } + CONTRACT_END; + + if (!m_pRCWRefCache) { + NewHolder pRCWRefCache = new RCWRefCache(this); + if (FastInterlockCompareExchangePointer(&m_pRCWRefCache, (RCWRefCache *)pRCWRefCache, NULL) == NULL) + { + pRCWRefCache.SuppressRelease(); + } + } + RETURN m_pRCWRefCache; +} + +RCWCache *AppDomain::CreateRCWCache() +{ + CONTRACT(RCWCache*) + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + POSTCONDITION(CheckPointer(RETVAL)); + } + CONTRACT_END; + + // Initialize the global RCW cleanup list here as well. This is so that it + // it guaranteed to exist if any RCW's are created, but it is not created + // unconditionally. + if (!g_pRCWCleanupList) + { + SystemDomain::LockHolder lh; + + if (!g_pRCWCleanupList) + g_pRCWCleanupList = new RCWCleanupList(); + } + _ASSERTE(g_pRCWCleanupList); + + { + BaseDomain::LockHolder lh(this); + + if (!m_pRCWCache) + m_pRCWCache = new RCWCache(this); + } + + RETURN m_pRCWCache; +} + +void AppDomain::ReleaseRCWs(LPVOID pCtxCookie) +{ + WRAPPER_NO_CONTRACT; + if (m_pRCWCache) + m_pRCWCache->ReleaseWrappersWorker(pCtxCookie); + + RemoveWinRTFactoryObjects(pCtxCookie); +} + +void AppDomain::DetachRCWs() +{ + WRAPPER_NO_CONTRACT; + if (m_pRCWCache) + m_pRCWCache->DetachWrappersWorker(); +} + +#endif // FEATURE_COMINTEROP + +BOOL AppDomain::CanThreadEnter(Thread *pThread) +{ + WRAPPER_NO_CONTRACT; + + if (m_Stage < STAGE_EXITED) + return TRUE; + + if (pThread == SystemDomain::System()->GetUnloadingThread()) + return m_Stage < STAGE_FINALIZING; + if (pThread == FinalizerThread::GetFinalizerThread()) + return m_Stage < STAGE_FINALIZED; + + return FALSE; +} + +void AppDomain::AllowThreadEntrance(AppDomain * pApp) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + FORBID_FAULT; + PRECONDITION(CheckPointer(pApp)); + } + CONTRACTL_END; + + if (pApp->GetUnloadRequestThread() == NULL) + { + // This is asynchonous unload, either by a host, or by AppDomain.Unload from AD unload event. + if (!pApp->IsUnloadingFromUnloadEvent()) + { + pApp->SetStage(STAGE_UNLOAD_REQUESTED); + pApp->EnableADUnloadWorker( + pApp->IsRudeUnload()?EEPolicy::ADU_Rude:EEPolicy::ADU_Safe); + return; + } + } + + SystemDomain::LockHolder lh; // we don't want to reopen appdomain if other thread can be preparing to unload it + +#ifdef FEATURE_COMINTEROP + if (pApp->m_pComCallWrapperCache) + pApp->m_pComCallWrapperCache->ResetDomainIsUnloading(); +#endif // FEATURE_COMINTEROP + + pApp->SetStage(STAGE_OPEN); +} + +void AppDomain::RestrictThreadEntrance(AppDomain * pApp) +{ + CONTRACTL + { + DISABLED(NOTHROW); + DISABLED(GC_TRIGGERS); + MODE_ANY; + DISABLED(FORBID_FAULT); + PRECONDITION(CheckPointer(pApp)); + } + CONTRACTL_END; + +#ifdef FEATURE_COMINTEROP + // Set the flag on our CCW cache so stubs won't enter + if (pApp->m_pComCallWrapperCache) + pApp->m_pComCallWrapperCache->SetDomainIsUnloading(); +#endif // FEATURE_COMINTEROP + + SystemDomain::LockHolder lh; // we don't want to reopen appdomain if other thread can be preparing to unload it + // Release our ID so remoting and thread pool won't enter + pApp->SetStage(STAGE_EXITED); +}; + +void AppDomain::Exit(BOOL fRunFinalizers, BOOL fAsyncExit) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + LOG((LF_APPDOMAIN | LF_CORDB, LL_INFO10, "AppDomain::Exiting domain [%d] %#08x %ls\n", + GetId().m_dwId, this, GetFriendlyNameForLogging())); + + RestrictEnterHolder RestrictEnter(this); + + { + SystemDomain::LockHolder lh; // we don't want to close appdomain if other thread can be preparing to unload it + SetStage(STAGE_EXITING); // Note that we're trying to exit + } + + // Raise the event indicating the domain is being unloaded. + if (GetDefaultContext()) + { + FastInterlockExchangePointer(&s_pAppDomainToRaiseUnloadEvent, this); + + DWORD timeout = GetEEPolicy()->GetTimeout(m_fRudeUnload?OPR_AppDomainRudeUnload : OPR_AppDomainUnload); + //if (timeout == INFINITE) + //{ + // timeout = 20000; // 20 seconds + //} + DWORD timeoutForFinalizer = GetEEPolicy()->GetTimeout(OPR_FinalizerRun); + ULONGLONG curTime = CLRGetTickCount64(); + ULONGLONG endTime = 0; + if (timeout != INFINITE) + { + endTime = curTime + timeout; + // We will try to kill AD unload event if it takes too long, and then we move on to the next registered caller. + timeout /= 5; + } + + while (s_pAppDomainToRaiseUnloadEvent != NULL) + { + FinalizerThread::FinalizerThreadWait(s_fProcessUnloadDomainEvent?timeout:timeoutForFinalizer); + if (endTime != 0 && s_pAppDomainToRaiseUnloadEvent != NULL) + { + if (CLRGetTickCount64() >= endTime) + { + SString sThreadId; + sThreadId.Printf(W("%x"), FinalizerThread::GetFinalizerThread()->GetThreadId()); + COMPlusThrow(kCannotUnloadAppDomainException, + IDS_EE_ADUNLOAD_CANT_UNWIND_THREAD, + sThreadId); + } + } + } + } + + // + // Set up blocks so no threads can enter except for the finalizer and the thread + // doing the unload. + // + + RestrictThreadEntrance(this); + + // Cause existing threads to abort out of this domain. This should ensure all + // normal threads are outside the domain, and we've already ensured that no new threads + // can enter. + + PerAppDomainTPCountList::AppDomainUnloadingHolder tpAdUnloadHolder(GetTPIndex()); + + + if (!NingenEnabled()) + { + UnwindThreads(); + } + + TESTHOOKCALL(UnwoundThreads(GetId().m_dwId)) ; + ProcessEventForHost(Event_DomainUnload, (PVOID)(UINT_PTR)GetId().m_dwId); + + RestrictEnter.SuppressRelease(); //after this point we don't guarantee appdomain consistency +#ifdef PROFILING_SUPPORTED + // Signal profile if present. + { + BEGIN_PIN_PROFILER(CORProfilerTrackAppDomainLoads()); + GCX_PREEMP(); + g_profControlBlock.pProfInterface->AppDomainShutdownStarted((AppDomainID) this); + END_PIN_PROFILER(); + } +#endif // PROFILING_SUPPORTED + COUNTER_ONLY(GetPerfCounters().m_Loading.cAppDomains--); + COUNTER_ONLY(GetPerfCounters().m_Loading.cAppDomainsUnloaded++); + + LOG((LF_APPDOMAIN | LF_CORDB, LL_INFO10, "AppDomain::Domain [%d] %#08x %ls is exited.\n", + GetId().m_dwId, this, GetFriendlyNameForLogging())); + + ReJitManager::OnAppDomainExit(this); + + // Send ETW events for this domain's unload and potentially iterate through this + // domain's modules & assemblies to send events for their unloads as well. This + // needs to occur before STAGE_FINALIZED (to ensure everything is there), so we do + // this before any finalization occurs at all. + ETW::LoaderLog::DomainUnload(this); + + // + // Spin running finalizers until we flush them all. We need to make multiple passes + // in case the finalizers create more finalizable objects. This is important to clear + // the finalizable objects as roots, as well as to actually execute the finalizers. This + // will only finalize instances instances of types that aren't potentially agile becuase we can't + // risk finalizing agile objects. So we will be left with instances of potentially agile types + // in handles or statics. + // + // @todo: Need to ensure this will terminate in a reasonable amount of time. Eventually + // we should probably start passing FALSE for fRunFinalizers. Also I'm not sure we + // guarantee that FinalizerThreadWait will ever terminate in general. + // + + SetStage(STAGE_FINALIZING); + + // Flush finalizers now. + FinalizerThread::UnloadAppDomain(this, fRunFinalizers); + + DWORD timeout = GetEEPolicy()->GetTimeout(m_fRudeUnload?OPR_AppDomainRudeUnload : OPR_AppDomainUnload); + ULONGLONG startTime = CLRGetTickCount64(); + ULONGLONG elapsedTime = 0; + DWORD finalizerWait = 0; + + while (FinalizerThread::GetUnloadingAppDomain() != NULL) + { + + if (timeout != INFINITE) + { + elapsedTime = CLRGetTickCount64() - startTime; + } + if (timeout > elapsedTime) + { + finalizerWait = timeout - static_cast(elapsedTime); + } + FinalizerThread::FinalizerThreadWait(finalizerWait); //will set stage to finalized + if (timeout != INFINITE && FinalizerThread::GetUnloadingAppDomain() != NULL) + { + elapsedTime = CLRGetTickCount64() - startTime; + if (timeout <= elapsedTime) + { + SetRudeUnload(); + // TODO: Consider escalation from RudeAppDomain + timeout = INFINITE; + } + } + } + + tpAdUnloadHolder.SuppressRelease(); + PerAppDomainTPCountList::ResetAppDomainTPCounts(GetTPIndex()); + + LOG((LF_APPDOMAIN | LF_CORDB, LL_INFO10, "AppDomain::Domain [%d] %#08x %ls is finalized.\n", + GetId().m_dwId, this, GetFriendlyNameForLogging())); + + + AppDomainRefHolder This(this); + AddRef(); // Hold a reference so CloseDomain won't delete us yet + CloseDomain(); // Remove ourself from the list of app domains + + // This needs to be done prior to destroying the handle tables below. + ReleaseDomainBoundInfo(); + + // + // It should be impossible to run non-mscorlib code in this domain now. + // Cleanup all of our roots except the handles. We do this to allow as many + // finalizers as possible to run correctly. If we delete the handles, they + // can't run. + // + if (!NingenEnabled()) + { +#ifdef FEATURE_REMOTING + EX_TRY + { + ADID domainId = GetId(); + MethodDescCallSite domainUnloaded(METHOD__REMOTING_SERVICES__DOMAIN_UNLOADED); + + ARG_SLOT args[1]; + args[0] = domainId.m_dwId; + domainUnloaded.Call(args); + } + EX_CATCH + { + //we don't care if it fails + } + EX_END_CATCH(SwallowAllExceptions); +#endif // FEATURE_REMOTING + } + + ClearGCRoots(); + ClearGCHandles(); + + LOG((LF_APPDOMAIN | LF_CORDB, LL_INFO10, "AppDomain::Domain [%d] %#08x %ls is cleared.\n", + GetId().m_dwId, this, GetFriendlyNameForLogging())); + + if (fAsyncExit && fRunFinalizers) + { + GCX_PREEMP(); + m_AssemblyCache.Clear(); + ClearFusionContext(); + ReleaseFiles(); + if (!NingenEnabled()) + { + AddMemoryPressure(); + } + } + SystemDomain::System()->AddToDelayedUnloadList(this, fAsyncExit); + SystemDomain::SetUnloadDomainCleared(); + if (m_dwId.m_dwId!=0) + SystemDomain::ReleaseAppDomainId(m_dwId); +#ifdef PROFILING_SUPPORTED + // Always signal profile if present, even when failed. + { + BEGIN_PIN_PROFILER(CORProfilerTrackAppDomainLoads()); + GCX_PREEMP(); + g_profControlBlock.pProfInterface->AppDomainShutdownFinished((AppDomainID) this, S_OK); + END_PIN_PROFILER(); + } +#endif // PROFILING_SUPPORTED + +} + +void AppDomain::Close() +{ + CONTRACTL + { + GC_TRIGGERS; + NOTHROW; + } + CONTRACTL_END; + + LOG((LF_APPDOMAIN | LF_CORDB, LL_INFO10, "AppDomain::Domain [%d] %#08x %ls is collected.\n", + GetId().m_dwId, this, GetFriendlyNameForLogging())); + + +#if CHECK_APP_DOMAIN_LEAKS + if (g_pConfig->AppDomainLeaks()) + // at this point shouldn't have any non-agile objects in the heap because we finalized all the non-agile ones. + SyncBlockCache::GetSyncBlockCache()->CheckForUnloadedInstances(GetIndex()); +#endif // CHECK_APP_DOMAIN_LEAKS + { + GCX_PREEMP(); + RemoveMemoryPressure(); + } + _ASSERTE(m_cRef>0); //should be alive at this point otherwise iterator can revive us and crash + { + SystemDomain::LockHolder lh; // Avoid races with AppDomainIterator + SetStage(STAGE_CLOSED); + } + + // CONSIDER: move releasing remoting cache from managed code to here. +} + + +void AppDomain::ResetUnloadRequestThread(ADID Id) +{ + CONTRACTL + { + NOTHROW; + MODE_ANY; + PRECONDITION(!IsADUnloadHelperThread()); + } + CONTRACTL_END; + + GCX_COOP(); + AppDomainFromIDHolder ad(Id, TRUE); + if(!ad.IsUnloaded() && ad->m_Stage < STAGE_UNLOAD_REQUESTED) + { + Thread *pThread = ad->GetUnloadRequestThread(); + if(pThread==GetThread()) + { + ad->m_dwThreadsStillInAppDomain=(ULONG)-1; + + if(pThread) + { + if (pThread->GetUnloadBoundaryFrame() && pThread->IsBeingAbortedForADUnload()) + { + pThread->UnmarkThreadForAbort(Thread::TAR_ADUnload); + } + ad->GetUnloadRequestThread()->ResetUnloadBoundaryFrame(); + pThread->ResetBeginAbortedForADUnload(); + } + + ad->SetUnloadRequestThread(NULL); + } + } +} + + +int g_fADUnloadWorkerOK = -1; + +HRESULT AppDomain::UnloadById(ADID dwId, BOOL fSync,BOOL fExceptionsPassThrough) +{ + CONTRACTL + { + if(fExceptionsPassThrough) {THROWS;} else {NOTHROW;} + MODE_ANY; + if (GetThread()) {GC_TRIGGERS;} else {DISABLED(GC_TRIGGERS);} + FORBID_FAULT; + } + CONTRACTL_END; + + if (dwId==(ADID)DefaultADID) + return COR_E_CANNOTUNLOADAPPDOMAIN; + + Thread *pThread = GetThread(); + + // Finalizer thread can not wait until AD unload is done, + // because AD unload is going to wait for Finalizer Thread. + if (fSync && pThread == FinalizerThread::GetFinalizerThread() && + !pThread->HasThreadStateNC(Thread::TSNC_RaiseUnloadEvent)) + return COR_E_CANNOTUNLOADAPPDOMAIN; + + + // AD unload helper thread should have been created. + _ASSERTE (g_fADUnloadWorkerOK == 1); + + _ASSERTE (!IsADUnloadHelperThread()); + + BOOL fIsRaisingUnloadEvent = (pThread != NULL && pThread->HasThreadStateNC(Thread::TSNC_RaiseUnloadEvent)); + + if (fIsRaisingUnloadEvent) + { + AppDomainFromIDHolder pApp(dwId, TRUE, AppDomainFromIDHolder::SyncType_GC); + + if (pApp.IsUnloaded() || ! pApp->CanLoadCode() || pApp->GetId().m_dwId == 0) + return COR_E_APPDOMAINUNLOADED; + + pApp->EnableADUnloadWorker(); + + return S_FALSE; + } + + + ADUnloadSinkHolder pSink; + + { + SystemDomain::LockHolder ulh; + + AppDomainFromIDHolder pApp(dwId, TRUE, AppDomainFromIDHolder::SyncType_ADLock); + + if (pApp.IsUnloaded() || ! pApp->CanLoadCode() || pApp->GetId().m_dwId == 0) + return COR_E_APPDOMAINUNLOADED; + + if (g_fADUnloadWorkerOK != 1) + { + _ASSERTE(FALSE); + return E_UNEXPECTED; + } + + if (!fSync) + { + pApp->EnableADUnloadWorker(); + return S_OK; + } + + pSink = pApp->PrepareForWaitUnloadCompletion(); + + pApp->EnableADUnloadWorker(); + + // release the holders - we don't care anymore if the appdomain is gone + } + +#ifdef FEATURE_TESTHOOKS + if (fExceptionsPassThrough) + { + CONTRACT_VIOLATION(FaultViolation); + return UnloadWaitNoCatch(dwId,pSink); + } +#endif + + return UnloadWait(dwId,pSink); +} + +HRESULT AppDomain::UnloadWait(ADID Id, ADUnloadSink * pSink) +{ + CONTRACTL + { + NOTHROW; + MODE_ANY; + if (GetThread()) {GC_TRIGGERS;} else {DISABLED(GC_TRIGGERS);} + } + CONTRACTL_END; + + HRESULT hr=S_OK; + EX_TRY + { + // IF you ever try to change this to something not using events, please address the fact that + // AppDomain::StopEEAndUnwindThreads relies on that events are used. + + pSink->WaitUnloadCompletion(); + } + EX_CATCH_HRESULT(hr); + + if (SUCCEEDED(hr)) + hr=pSink->GetUnloadResult(); + + if (FAILED(hr)) + { + ResetUnloadRequestThread(Id); + } + return hr; +} + +#ifdef FEATURE_TESTHOOKS +HRESULT AppDomain::UnloadWaitNoCatch(ADID Id, ADUnloadSink * pSink) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_MODE_ANY; + + Holder, AppDomain::ResetUnloadRequestThread> resetUnloadHolder(Id); + + // IF you ever try to change this to something not using events, please address the fact that + // AppDomain::StopEEAndUnwindThreads relies on that events are used. + pSink->WaitUnloadCompletion(); + + HRESULT hr = pSink->GetUnloadResult(); + + if (SUCCEEDED(hr)) + resetUnloadHolder.SuppressRelease(); + + return hr; +} +#endif + +void AppDomain::Unload(BOOL fForceUnload) +{ + CONTRACTL + { + THROWS; + MODE_COOPERATIVE; + GC_TRIGGERS; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + +#ifdef FEATURE_MULTICOREJIT + + // Avoid profiling file is partially written in ASP.net scenarios, call it earlier + GetMulticoreJitManager().StopProfile(true); + +#endif + + Thread *pThread = GetThread(); + + + if (! fForceUnload && !g_pConfig->AppDomainUnload()) + return; + + EPolicyAction action; + EClrOperation operation; + if (!IsRudeUnload()) + { + operation = OPR_AppDomainUnload; + } + else + { + operation = OPR_AppDomainRudeUnload; + } + action = GetEEPolicy()->GetDefaultAction(operation,NULL); + GetEEPolicy()->NotifyHostOnDefaultAction(operation,action); + + switch (action) + { + case eUnloadAppDomain: + break; + case eRudeUnloadAppDomain: + SetRudeUnload(); + break; + case eExitProcess: + case eFastExitProcess: + case eRudeExitProcess: + case eDisableRuntime: + EEPolicy::HandleExitProcessFromEscalation(action, HOST_E_EXITPROCESS_ADUNLOAD); + _ASSERTE (!"Should not get here"); + break; + default: + break; + } + +#if (defined(_DEBUG) || defined(BREAK_ON_UNLOAD) || defined(AD_LOG_MEMORY) || defined(AD_SNAPSHOT)) + static int unloadCount = 0; +#endif + +#ifdef AD_LOG_MEMORY + { + GCX_PREEMP(); + static int logMemory = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ADLogMemory); + typedef void (__cdecl *LogItFcn) ( int ); + static LogItFcn pLogIt = NULL; + + if (logMemory && ! pLogIt) + { + HMODULE hMod = CLRLoadLibrary(W("mpdh.dll")); + if (hMod) + { + pLogIt = (LogItFcn)GetProcAddress(hMod, "logIt"); + if (pLogIt) + { + pLogIt(9999); + pLogIt(9999); + } + } + } + } +#endif // AD_LOG_MEMORY + + if (IsDefaultDomain() && !IsSingleAppDomain()) + COMPlusThrow(kCannotUnloadAppDomainException, IDS_EE_ADUNLOAD_DEFAULT); + + _ASSERTE(CanUnload()); + + if (pThread == FinalizerThread::GetFinalizerThread() || GetUnloadRequestThread() == FinalizerThread::GetFinalizerThread()) + COMPlusThrow(kCannotUnloadAppDomainException, IDS_EE_ADUNLOAD_IN_FINALIZER); + + _ASSERTE(! SystemDomain::AppDomainBeingUnloaded()); + + // should not be running in this AD because unload spawned thread in default domain + if (!NingenEnabled()) + { + _ASSERTE(!pThread->IsRunningIn(this, NULL)); + } + + +#ifdef APPDOMAIN_STATE + _ASSERTE_ALL_BUILDS("clr/src/VM/AppDomain.cpp", pThread->GetDomain()->IsDefaultDomain()); +#endif + + LOG((LF_APPDOMAIN | LF_CORDB, LL_INFO10, "AppDomain::Unloading domain [%d] %#08x %ls\n", GetId().m_dwId, this, GetFriendlyName())); + + STRESS_LOG3 (LF_APPDOMAIN, LL_INFO100, "Unload domain [%d, %d] %p\n", GetId().m_dwId, GetIndex().m_dwIndex, this); + + UnloadHolder hold(this); + + SystemDomain::System()->SetUnloadRequestingThread(GetUnloadRequestThread()); + SystemDomain::System()->SetUnloadingThread(pThread); + + +#ifdef _DEBUG + static int dumpSB = -1; + + if (dumpSB == -1) + dumpSB = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ADDumpSB); + + if (dumpSB > 1) + { + LogSpewAlways("Starting unload %3.3d\n", unloadCount); + DumpSyncBlockCache(); + } +#endif // _DEBUG + + BOOL bForceGC=m_bForceGCOnUnload; + +#ifdef AD_LOG_MEMORY + if (pLogIt) + bForceGC=TRUE; +#endif // AD_LOG_MEMORY + +#ifdef AD_SNAPSHOT + static int takeSnapShot = -1; + + if (takeSnapShot == -1) + takeSnapShot = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ADTakeSnapShot); + + if (takeSnapShot) + bForceGC=TRUE; +#endif // AD_SNAPSHOT + +#ifdef _DEBUG + if (dumpSB > 0) + bForceGC=TRUE; +#endif // _DEBUG + static int cfgForceGC = -1; + + if (cfgForceGC == -1) + cfgForceGC =!CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_ADULazyMemoryRelease); + + bForceGC=bForceGC||cfgForceGC; + AppDomainRefHolder This(this); + AddRef(); + + // Do the actual unloading + { + // We do not want other threads to abort the current one. + ThreadPreventAsyncHolder preventAsync; + Exit(TRUE, !bForceGC); + } + if(bForceGC) + { + GCHeap::GetGCHeap()->GarbageCollect(); + FinalizerThread::FinalizerThreadWait(); + SetStage(STAGE_COLLECTED); + Close(); //NOTHROW! + } + +#ifdef AD_LOG_MEMORY + if (pLogIt) + { + GCX_PREEMP(); + pLogIt(unloadCount); + } +#endif // AD_LOG_MEMORY + +#ifdef AD_SNAPSHOT + if (takeSnapShot) + { + char buffer[1024]; + sprintf(buffer, "vadump -p %d -o > vadump.%d", GetCurrentProcessId(), unloadCount); + system(buffer); + sprintf(buffer, "umdh -p:%d -d -i:1 -f:umdh.%d", GetCurrentProcessId(), unloadCount); + system(buffer); + int takeDHSnapShot = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ADTakeDHSnapShot); + if (takeDHSnapShot) + { + sprintf(buffer, "dh -p %d -s -g -h -b -f dh.%d", GetCurrentProcessId(), unloadCount); + system(buffer); + } + } +#endif // AD_SNAPSHOT + +#ifdef _DEBUG + if (dumpSB > 0) + { + // do extra finalizer wait to remove any leftover sb entries + FinalizerThread::FinalizerThreadWait(); + GCHeap::GetGCHeap()->GarbageCollect(); + FinalizerThread::FinalizerThreadWait(); + LogSpewAlways("Done unload %3.3d\n", unloadCount); + DumpSyncBlockCache(); + ShutdownLogging(); + WCHAR buffer[128]; + swprintf_s(buffer, NumItems(buffer), W("DumpSB.%d"), unloadCount); + _ASSERTE(WszMoveFileEx(W("COMPLUS.LOG"), buffer, MOVEFILE_REPLACE_EXISTING)); + // this will open a new file + InitLogging(); + } +#endif // _DEBUG +} + +void AppDomain::ExceptionUnwind(Frame *pFrame) +{ + CONTRACTL + { + DISABLED(GC_TRIGGERS); // EEResourceException + DISABLED(THROWS); // EEResourceException + MODE_ANY; + } + CONTRACTL_END; + + LOG((LF_APPDOMAIN, LL_INFO10, "AppDomain::ExceptionUnwind for %8.8x\n", pFrame)); +#if _DEBUG_ADUNLOAD + printf("%x AppDomain::ExceptionUnwind for %8.8p\n", GetThread()->GetThreadId(), pFrame); +#endif + Thread *pThread = GetThread(); + _ASSERTE(pThread); + + if (! pThread->ShouldChangeAbortToUnload(pFrame)) + { + LOG((LF_APPDOMAIN, LL_INFO10, "AppDomain::ExceptionUnwind: not first transition or abort\n")); + return; + } + + LOG((LF_APPDOMAIN, LL_INFO10, "AppDomain::ExceptionUnwind: changing to unload\n")); + + GCX_COOP(); + OBJECTREF throwable = NULL; + EEResourceException e(kAppDomainUnloadedException, W("Remoting_AppDomainUnloaded_ThreadUnwound")); + throwable = e.GetThrowable(); + + // reset the exception to an AppDomainUnloadedException + if (throwable != NULL) + { + GetThread()->SafeSetThrowables(throwable); + } +} + +BOOL AppDomain::StopEEAndUnwindThreads(unsigned int retryCount, BOOL *pFMarkUnloadRequestThread) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + SO_INTOLERANT; + } + CONTRACTL_END; + + Thread *pThread = NULL; + DWORD nThreadsNeedMoreWork=0; + if (retryCount != (unsigned int)-1 && retryCount < g_pConfig->AppDomainUnloadRetryCount()) + { + Thread *pCurThread = GetThread(); + if (pCurThread->CatchAtSafePoint()) + pCurThread->PulseGCMode(); + + { + // We know which thread is not in the domain now. We just need to + // work on those threads. We do not need to suspend the runtime. + ThreadStoreLockHolder tsl; + + while ((pThread = ThreadStore::GetThreadList(pThread)) != NULL) + { + if (pThread == pCurThread) + { + continue; + } + + if (pThread == FinalizerThread::GetFinalizerThread()) + { + continue; + } + + if (pThread->GetUnloadBoundaryFrame() == NULL) + { + continue; + } + + // A thread may have UnloadBoundaryFrame set if + // 1. Being unloaded by AD unload helper thread + // 2. Escalation from OOM or SO triggers AD unload + // Here we only need to work on threads that are in the domain. If we work on other threads, + // those threads may be stucked in a finally, and we will not be able to escalate for them, + // therefore AD unload is blocked. + if (pThread->IsBeingAbortedForADUnload() || + pThread == SystemDomain::System()->GetUnloadRequestingThread()) + { + nThreadsNeedMoreWork++; + } + + if (!(IsRudeUnload() || + (pThread != SystemDomain::System()->GetUnloadRequestingThread() || OnlyOneThreadLeft()))) + { + continue; + } + + if ((pThread == SystemDomain::System()->GetUnloadRequestingThread()) && *pFMarkUnloadRequestThread) + { + // Mark thread for abortion only once; later on interrupt only + *pFMarkUnloadRequestThread = FALSE; + pThread->SetAbortRequest(m_fRudeUnload? EEPolicy::TA_Rude : EEPolicy::TA_V1Compatible); + } + else + { + if (pThread->m_State & Thread::TS_Interruptible) + { + pThread->UserInterrupt(Thread::TI_Abort); + } + } + + if (pThread->PreemptiveGCDisabledOther()) + { + #if defined(FEATURE_HIJACK) && !defined(PLATFORM_UNIX) + Thread::SuspendThreadResult str = pThread->SuspendThread(); + if (str == Thread::STR_Success) + { + if (pThread->PreemptiveGCDisabledOther() && + (!pThread->IsAbortInitiated() || pThread->IsRudeAbort())) + { + pThread->HandleJITCaseForAbort(); + } + pThread->ResumeThread(); + } + #endif + } + } + } // ThreadStoreLockHolder + + if (nThreadsNeedMoreWork && CLRTaskHosted()) + { + // In case a thread is the domain is blocked due to its scheduler being + // occupied by another thread. + Thread::ThreadAbortWatchDog(); + } + m_dwThreadsStillInAppDomain=nThreadsNeedMoreWork; + return !nThreadsNeedMoreWork; + } + + // For now piggyback on the GC's suspend EE mechanism + ThreadSuspend::SuspendEE(ThreadSuspend::SUSPEND_FOR_APPDOMAIN_SHUTDOWN); +#ifdef _DEBUG + // @todo: what to do with any threads that didn't stop? + _ASSERTE(ThreadStore::s_pThreadStore->DbgBackgroundThreadCount() > 0); +#endif // _DEBUG + + int totalADCount = 0; + int finalizerADCount = 0; + pThread = NULL; + + RuntimeExceptionKind reKind = kLastException; + UINT resId = 0; + SmallStackSString ssThreadId; + + while ((pThread = ThreadStore::GetThreadList(pThread)) != NULL) + { + // we already checked that we're not running in the unload domain + if (pThread == GetThread()) + { + continue; + } + +#ifdef _DEBUG + void PrintStackTraceWithADToLog(Thread *pThread); + if (LoggingOn(LF_APPDOMAIN, LL_INFO100)) { + LOG((LF_APPDOMAIN, LL_INFO100, "\nStackTrace for %x\n", pThread->GetThreadId())); + PrintStackTraceWithADToLog(pThread); + } +#endif // _DEBUG + int count = 0; + Frame *pFrame = pThread->GetFirstTransitionInto(this, &count); + if (! pFrame) { + _ASSERTE(count == 0); + if (pThread->IsBeingAbortedForADUnload()) + { + pThread->ResetBeginAbortedForADUnload(); + } + continue; + } + + if (pThread != FinalizerThread::GetFinalizerThread()) + { + totalADCount += count; + nThreadsNeedMoreWork++; + pThread->SetUnloadBoundaryFrame(pFrame); + } + else + { + finalizerADCount = count; + } + + // don't setup the exception info for the unloading thread unless it's the last one in + if (retryCount != ((unsigned int) -1) && retryCount > g_pConfig->AppDomainUnloadRetryCount() && reKind == kLastException && + (pThread != SystemDomain::System()->GetUnloadRequestingThread() || OnlyOneThreadLeft())) + { +#ifdef AD_BREAK_ON_CANNOT_UNLOAD + static int breakOnCannotUnload = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ADBreakOnCannotUnload); + if (breakOnCannotUnload) + _ASSERTE(!"Cannot unload AD"); +#endif // AD_BREAK_ON_CANNOT_UNLOAD + reKind = kCannotUnloadAppDomainException; + resId = IDS_EE_ADUNLOAD_CANT_UNWIND_THREAD; + ssThreadId.Printf(W("%x"), pThread->GetThreadId()); + STRESS_LOG2(LF_APPDOMAIN, LL_INFO10, "AppDomain::UnwindThreads cannot stop thread %x with %d transitions\n", pThread->GetThreadId(), count); + // don't break out of this early or the assert totalADCount == (int)m_dwThreadEnterCount below will fire + // it's better to chew a little extra time here and make sure our counts are consistent + } + // only abort the thread requesting the unload if it's the last one in, that way it will get + // notification that the unload failed for some other thread not being aborted. And don't abort + // the finalizer thread - let it finish it's work as it's allowed to be in there. If it won't finish, + // then we will eventually get a CannotUnloadException on it. + + if (pThread != FinalizerThread::GetFinalizerThread() && + // If the domain is rudely unloaded, we will unwind the requesting thread out + // Rude unload is going to succeed, or escalated to disable runtime or higher. + (IsRudeUnload() || + (pThread != SystemDomain::System()->GetUnloadRequestingThread() || OnlyOneThreadLeft()) + ) + ) + { + + STRESS_LOG2(LF_APPDOMAIN, LL_INFO100, "AppDomain::UnwindThreads stopping %x with %d transitions\n", pThread->GetThreadId(), count); + LOG((LF_APPDOMAIN, LL_INFO100, "AppDomain::UnwindThreads stopping %x with %d transitions\n", pThread->GetThreadId(), count)); +#if _DEBUG_ADUNLOAD + printf("AppDomain::UnwindThreads %x stopping %x with first frame %8.8p\n", GetThread()->GetThreadId(), pThread->GetThreadId(), pFrame); +#endif + if (pThread == SystemDomain::System()->GetUnloadRequestingThread()) + { + // Mark thread for abortion only once; later on interrupt only + *pFMarkUnloadRequestThread = FALSE; + } + pThread->SetAbortRequest(m_fRudeUnload? EEPolicy::TA_Rude : EEPolicy::TA_V1Compatible); + } + TESTHOOKCALL(UnwindingThreads(GetId().m_dwId)) ; + } + _ASSERTE(totalADCount + finalizerADCount == (int)m_dwThreadEnterCount); + + //@TODO: This is intended to catch a stress bug. Remove when no longer needed. + if (totalADCount + finalizerADCount != (int)m_dwThreadEnterCount) + FreeBuildDebugBreak(); + + // if our count did get messed up, set it to whatever count we actually found in the domain to avoid looping + // or other problems related to incorrect count. This is very much a bug if this happens - a thread should always + // exit the domain gracefully. + // m_dwThreadEnterCount = totalADCount; + + if (reKind != kLastException) + { + pThread = NULL; + while ((pThread = ThreadStore::GetThreadList(pThread)) != NULL) + { + if (pThread->IsBeingAbortedForADUnload()) + { + pThread->ResetBeginAbortedForADUnload(); + } + } + } + + // CommonTripThread will handle the abort for any threads that we've marked + ThreadSuspend::RestartEE(FALSE, TRUE); + if (reKind != kLastException) + COMPlusThrow(reKind, resId, ssThreadId.GetUnicode()); + + _ASSERTE((totalADCount==0 && nThreadsNeedMoreWork==0) ||(totalADCount!=0 && nThreadsNeedMoreWork!=0)); + + m_dwThreadsStillInAppDomain=nThreadsNeedMoreWork; + return (totalADCount == 0); +} + +void AppDomain::UnwindThreads() +{ + // This function should guarantee appdomain + // consistency even if it fails. Everything that is going + // to make the appdomain impossible to reenter + // should be factored out + + // @todo: need real synchronization here!!! + CONTRACTL + { + MODE_COOPERATIVE; + THROWS; + GC_TRIGGERS; + } + CONTRACTL_END; + + int retryCount = -1; + m_dwThreadsStillInAppDomain=(ULONG)-1; + ULONGLONG startTime = CLRGetTickCount64(); + + if (GetEEPolicy()->GetDefaultAction(OPR_AppDomainUnload, NULL) == eRudeUnloadAppDomain && + !IsRudeUnload()) + { + GetEEPolicy()->NotifyHostOnDefaultAction(OPR_AppDomainUnload, eRudeUnloadAppDomain); + SetRudeUnload(); + } + + // Force threads to go through slow path during AD unload. + TSSuspendHolder shTrap; + + BOOL fCurrentUnloadMode = IsRudeUnload(); + BOOL fMarkUnloadRequestThread = TRUE; + + // now wait for all the threads running in our AD to get out + do + { + DWORD timeout = GetEEPolicy()->GetTimeout(m_fRudeUnload?OPR_AppDomainRudeUnload : OPR_AppDomainUnload); + EPolicyAction action = GetEEPolicy()->GetActionOnTimeout(m_fRudeUnload?OPR_AppDomainRudeUnload : OPR_AppDomainUnload, NULL); + if (timeout != INFINITE && action > eUnloadAppDomain) { + // Escalation policy specified. + ULONGLONG curTime = CLRGetTickCount64(); + ULONGLONG elapseTime = curTime - startTime; + if (elapseTime > timeout) + { + // Escalate + switch (action) + { + case eRudeUnloadAppDomain: + GetEEPolicy()->NotifyHostOnTimeout(m_fRudeUnload?OPR_AppDomainRudeUnload : OPR_AppDomainUnload, action); + SetRudeUnload(); + STRESS_LOG1(LF_APPDOMAIN, LL_INFO100,"Escalating to RADU, adid=%d",GetId().m_dwId); + break; + case eExitProcess: + case eFastExitProcess: + case eRudeExitProcess: + case eDisableRuntime: + GetEEPolicy()->NotifyHostOnTimeout(m_fRudeUnload?OPR_AppDomainRudeUnload : OPR_AppDomainUnload, action); + EEPolicy::HandleExitProcessFromEscalation(action, HOST_E_EXITPROCESS_TIMEOUT); + _ASSERTE (!"Should not reach here"); + break; + default: + break; + } + } + } +#ifdef _DEBUG + if (LoggingOn(LF_APPDOMAIN, LL_INFO100)) + DumpADThreadTrack(); +#endif // _DEBUG + BOOL fNextUnloadMode = IsRudeUnload(); + if (fCurrentUnloadMode != fNextUnloadMode) + { + // We have changed from normal unload to rude unload. We need to mark the thread + // with RudeAbort, but we can only do this safely if the runtime is suspended. + fCurrentUnloadMode = fNextUnloadMode; + retryCount = -1; + } + if (StopEEAndUnwindThreads(retryCount, &fMarkUnloadRequestThread)) + break; + if (timeout != INFINITE) + { + // Turn off the timeout used by AD. + retryCount = 1; + } + else + { + // GCStress takes a long time to unwind, due to expensive creation of + // a threadabort exception. + if (!GCStress::IsEnabled()) + ++retryCount; + LOG((LF_APPDOMAIN, LL_INFO10, "AppDomain::UnwindThreads iteration %d waiting on thread count %d\n", retryCount, m_dwThreadEnterCount)); +#if _DEBUG_ADUNLOAD + printf("AppDomain::UnwindThreads iteration %d waiting on thread count %d\n", retryCount, m_dwThreadEnterCount); +#endif + } + + if (m_dwThreadEnterCount != 0) + { +#ifdef _DEBUG + GetThread()->UserSleep(20); +#else // !_DEBUG + GetThread()->UserSleep(10); +#endif // !_DEBUG + } + } + while (TRUE) ; +} + +void AppDomain::ClearGCHandles() +{ + CONTRACTL + { + GC_TRIGGERS; + MODE_COOPERATIVE; + NOTHROW; + } + CONTRACTL_END; + + SetStage(STAGE_HANDLETABLE_NOACCESS); + + GCHeap::GetGCHeap()->WaitUntilConcurrentGCComplete(); + + // Keep async pin handles alive by moving them to default domain + HandleAsyncPinHandles(); + + // Remove our handle table as a source of GC roots + HandleTableBucket *pBucket = m_hHandleTableBucket; + +#ifdef _DEBUG + if (((HandleTable *)(pBucket->pTable[0]))->uADIndex != m_dwIndex) + _ASSERTE (!"AD index mismatch"); +#endif // _DEBUG + + Ref_RemoveHandleTableBucket(pBucket); +} + +// When an AD is unloaded, we will release all objects in this AD. +// If a future asynchronous operation, like io completion port function, +// we need to keep the memory space fixed so that the gc heap is not corrupted. +void AppDomain::HandleAsyncPinHandles() +{ + CONTRACTL + { + GC_TRIGGERS; + MODE_COOPERATIVE; + NOTHROW; + } + CONTRACTL_END; + + HandleTableBucket *pBucket = m_hHandleTableBucket; + // IO completion port picks IO job using FIFO. Here is how we know which AsyncPinHandle can be freed. + // 1. We mark all non-pending AsyncPinHandle with READYTOCLEAN. + // 2. We queue a dump Overlapped to the IO completion as a marker. + // 3. When the Overlapped is picked up by completion port, we wait until all previous IO jobs are processed. + // 4. Then we can delete all AsyncPinHandle marked with READYTOCLEAN. + HandleTableBucket *pBucketInDefault = SystemDomain::System()->DefaultDomain()->m_hHandleTableBucket; + Ref_RelocateAsyncPinHandles(pBucket, pBucketInDefault); + + OverlappedDataObject::RequestCleanup(); +} + +void AppDomain::ClearGCRoots() +{ + CONTRACTL + { + GC_TRIGGERS; + MODE_COOPERATIVE; + NOTHROW; + } + CONTRACTL_END; + + Thread *pThread = NULL; + ThreadSuspend::SuspendEE(ThreadSuspend::SUSPEND_FOR_APPDOMAIN_SHUTDOWN); + + // Tell the JIT managers to delete any entries in their structures. All the cooperative mode threads are stopped at + // this point, so only need to synchronize the preemptive mode threads. + ExecutionManager::Unload(GetLoaderAllocator()); + + while ((pThread = ThreadStore::GetAllThreadList(pThread, 0, 0)) != NULL) + { + // Delete the thread local static store + pThread->DeleteThreadStaticData(this); + +#ifdef FEATURE_LEAK_CULTURE_INFO + pThread->ResetCultureForDomain(GetId()); +#endif // FEATURE_LEAK_CULTURE_INFO + + // @TODO: A pre-allocated AppDomainUnloaded exception might be better. + if (m_hHandleTableBucket->Contains(pThread->m_LastThrownObjectHandle)) + { + // Never delete a handle to a preallocated exception object. + if (!CLRException::IsPreallocatedExceptionHandle(pThread->m_LastThrownObjectHandle)) + { + DestroyHandle(pThread->m_LastThrownObjectHandle); + } + + pThread->m_LastThrownObjectHandle = NULL; + } + + // Clear out the exceptions objects held by a thread. + pThread->GetExceptionState()->ClearThrowablesForUnload(m_hHandleTableBucket); + } + + //delete them while we still have the runtime suspended + // This must be deleted before the loader heaps are deleted. + if (m_pMarshalingData != NULL) + { + delete m_pMarshalingData; + m_pMarshalingData = NULL; + } + + if (m_pLargeHeapHandleTable != NULL) + { + delete m_pLargeHeapHandleTable; + m_pLargeHeapHandleTable = NULL; + } + + ThreadSuspend::RestartEE(FALSE, TRUE); +} + +#ifdef _DEBUG + +void AppDomain::TrackADThreadEnter(Thread *pThread, Frame *pFrame) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + // REENTRANT + PRECONDITION(CheckPointer(pThread)); + PRECONDITION(pFrame != (Frame*)(size_t) INVALID_POINTER_CD); + } + CONTRACTL_END; + + while (FastInterlockCompareExchange((LONG*)&m_TrackSpinLock, 1, 0) != 0) + ; + if (m_pThreadTrackInfoList == NULL) + m_pThreadTrackInfoList = new (nothrow) ThreadTrackInfoList; + // If we don't assert here, we will AV in the for loop below + _ASSERTE(m_pThreadTrackInfoList); + + ThreadTrackInfoList *pTrackList= m_pThreadTrackInfoList; + + ThreadTrackInfo *pTrack = NULL; + int i; + for (i=0; i < pTrackList->Count(); i++) { + if ((*(pTrackList->Get(i)))->pThread == pThread) { + pTrack = *(pTrackList->Get(i)); + break; + } + } + if (! pTrack) { + pTrack = new (nothrow) ThreadTrackInfo; + // If we don't assert here, we will AV in the for loop below. + _ASSERTE(pTrack); + pTrack->pThread = pThread; + ThreadTrackInfo **pSlot = pTrackList->Append(); + *pSlot = pTrack; + } + + InterlockedIncrement((LONG*)&m_dwThreadEnterCount); + Frame **pSlot; + if (pTrack) + { + pSlot = pTrack->frameStack.Insert(0); + *pSlot = pFrame; + } + int totThreads = 0; + for (i=0; i < pTrackList->Count(); i++) + totThreads += (*(pTrackList->Get(i)))->frameStack.Count(); + _ASSERTE(totThreads == (int)m_dwThreadEnterCount); + + InterlockedExchange((LONG*)&m_TrackSpinLock, 0); +} + + +void AppDomain::TrackADThreadExit(Thread *pThread, Frame *pFrame) +{ + CONTRACTL + { + if (GetThread()) {MODE_COOPERATIVE;} + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + while (FastInterlockCompareExchange((LONG*)&m_TrackSpinLock, 1, 0) != 0) + ; + ThreadTrackInfoList *pTrackList= m_pThreadTrackInfoList; + _ASSERTE(pTrackList); + ThreadTrackInfo *pTrack = NULL; + int i; + for (i=0; i < pTrackList->Count(); i++) + { + if ((*(pTrackList->Get(i)))->pThread == pThread) + { + pTrack = *(pTrackList->Get(i)); + break; + } + } + _ASSERTE(pTrack); + _ASSERTE(*(pTrack->frameStack.Get(0)) == pFrame); + pTrack->frameStack.Delete(0); + InterlockedDecrement((LONG*)&m_dwThreadEnterCount); + + int totThreads = 0; + for (i=0; i < pTrackList->Count(); i++) + totThreads += (*(pTrackList->Get(i)))->frameStack.Count(); + _ASSERTE(totThreads == (int)m_dwThreadEnterCount); + + InterlockedExchange((LONG*)&m_TrackSpinLock, 0); +} + +void AppDomain::DumpADThreadTrack() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + while (FastInterlockCompareExchange((LONG*)&m_TrackSpinLock, 1, 0) != 0) + ; + ThreadTrackInfoList *pTrackList= m_pThreadTrackInfoList; + if (!pTrackList) + goto end; + + { + LOG((LF_APPDOMAIN, LL_INFO10000, "\nThread dump of %d threads for [%d] %#08x %S\n", + m_dwThreadEnterCount, GetId().m_dwId, this, GetFriendlyNameForLogging())); + int totThreads = 0; + for (int i=0; i < pTrackList->Count(); i++) + { + ThreadTrackInfo *pTrack = *(pTrackList->Get(i)); + if (pTrack->frameStack.Count()==0) + continue; + LOG((LF_APPDOMAIN, LL_INFO100, " ADEnterCount for %x is %d\n", pTrack->pThread->GetThreadId(), pTrack->frameStack.Count())); + totThreads += pTrack->frameStack.Count(); + for (int j=0; j < pTrack->frameStack.Count(); j++) + LOG((LF_APPDOMAIN, LL_INFO100, " frame %8.8x\n", *(pTrack->frameStack.Get(j)))); + } + _ASSERTE(totThreads == (int)m_dwThreadEnterCount); + } +end: + InterlockedExchange((LONG*)&m_TrackSpinLock, 0); +} +#endif // _DEBUG + +#ifdef FEATURE_REMOTING +OBJECTREF AppDomain::GetAppDomainProxy() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + OBJECTREF orProxy = CRemotingServices::CreateProxyForDomain(this); + + _ASSERTE(orProxy->IsTransparentProxy()); + + return orProxy; +} +#endif + +#endif // CROSSGEN_COMPILE + +void *SharedDomain::operator new(size_t size, void *pInPlace) +{ + LIMITED_METHOD_CONTRACT; + return pInPlace; +} + +void SharedDomain::operator delete(void *pMem) +{ + LIMITED_METHOD_CONTRACT; + // Do nothing - new() was in-place +} + + +void SharedDomain::Attach() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + // Create the global SharedDomain and initialize it. + m_pSharedDomain = new (&g_pSharedDomainMemory[0]) SharedDomain(); + SystemDomain::GetGlobalLoaderAllocator()->m_pDomain = m_pSharedDomain; + // This cannot fail since g_pSharedDomainMemory is a static array. + CONSISTENCY_CHECK(CheckPointer(m_pSharedDomain)); + + LOG((LF_CLASSLOADER, + LL_INFO10, + "Created shared domain at %p\n", + m_pSharedDomain)); + + // We need to initialize the memory pools etc. for the system domain. + m_pSharedDomain->Init(); // Setup the memory heaps + + // allocate a Virtual Call Stub Manager for the shared domain + m_pSharedDomain->InitVSD(); +} + +#ifndef CROSSGEN_COMPILE +void SharedDomain::Detach() +{ + if (m_pSharedDomain) + { + m_pSharedDomain->Terminate(); + delete m_pSharedDomain; + m_pSharedDomain = NULL; + } +} +#endif // CROSSGEN_COMPILE + +#endif // !DACCESS_COMPILE + +SharedDomain *SharedDomain::GetDomain() +{ + LIMITED_METHOD_DAC_CONTRACT; + + return m_pSharedDomain; +} + +#ifndef DACCESS_COMPILE + +#define INITIAL_ASSEMBLY_MAP_SIZE 17 +void SharedDomain::Init() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + BaseDomain::Init(); + +#ifdef FEATURE_LOADER_OPTIMIZATION + m_FileCreateLock.Init(CrstSharedAssemblyCreate, CRST_DEFAULT,TRUE); + + LockOwner lock = { &m_DomainCrst, IsOwnerOfCrst }; + m_assemblyMap.Init(INITIAL_ASSEMBLY_MAP_SIZE, CompareSharedAssembly, TRUE, &lock); +#endif // FEATURE_LOADER_OPTIMIZATION + + ETW::LoaderLog::DomainLoad(this); +} + +#ifndef CROSSGEN_COMPILE +void SharedDomain::Terminate() +{ + // make sure we delete the StringLiteralMap before unloading + // the asemblies since the string literal map entries can + // point to metadata string literals. + GetLoaderAllocator()->CleanupStringLiteralMap(); + +#ifdef FEATURE_LOADER_OPTIMIZATION + PtrHashMap::PtrIterator i = m_assemblyMap.begin(); + + while (!i.end()) + { + Assembly *pAssembly = (Assembly*) i.GetValue(); + delete pAssembly; + ++i; + } + + ListLockEntry* pElement; + pElement = m_FileCreateLock.Pop(TRUE); + while (pElement) + { +#ifdef STRICT_CLSINITLOCK_ENTRY_LEAK_DETECTION + _ASSERTE (dbg_fDrasticShutdown || g_fInControlC); +#endif + delete(pElement); + pElement = (FileLoadLock*) m_FileCreateLock.Pop(TRUE); + } + m_FileCreateLock.Destroy(); +#endif // FEATURE_LOADER_OPTIMIZATION + BaseDomain::Terminate(); +} +#endif // CROSSGEN_COMPILE + + + +#ifdef FEATURE_LOADER_OPTIMIZATION + +BOOL SharedDomain::CompareSharedAssembly(UPTR u1, UPTR u2) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + // This is the input to the lookup + SharedAssemblyLocator *pLocator = (SharedAssemblyLocator *) (u1<<1); + + // This is the value stored in the table + Assembly *pAssembly = (Assembly *) u2; + if (pLocator->GetType()==SharedAssemblyLocator::DOMAINASSEMBLY) + { + if (!pAssembly->GetManifestFile()->Equals(pLocator->GetDomainAssembly()->GetFile())) + return FALSE; + + return pAssembly->CanBeShared(pLocator->GetDomainAssembly()); + } + else + if (pLocator->GetType()==SharedAssemblyLocator::PEASSEMBLY) + return pAssembly->GetManifestFile()->Equals(pLocator->GetPEAssembly()); + else + if (pLocator->GetType()==SharedAssemblyLocator::PEASSEMBLYEXACT) + return pAssembly->GetManifestFile() == pLocator->GetPEAssembly(); + _ASSERTE(!"Unexpected type of assembly locator"); + return FALSE; +} + +DWORD SharedAssemblyLocator::Hash() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + if (m_type==DOMAINASSEMBLY) + return GetDomainAssembly()->HashIdentity(); + if (m_type==PEASSEMBLY||m_type==PEASSEMBLYEXACT) + return GetPEAssembly()->HashIdentity(); + _ASSERTE(!"Unexpected type of assembly locator"); + return 0; +} + +Assembly * SharedDomain::FindShareableAssembly(SharedAssemblyLocator * pLocator) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + Assembly * match= (Assembly *) m_assemblyMap.LookupValue(pLocator->Hash(), pLocator); + if (match != (Assembly *) INVALIDENTRY) + return match; + else + return NULL; +} + +SIZE_T SharedDomain::GetShareableAssemblyCount() +{ + LIMITED_METHOD_CONTRACT; + + return m_assemblyMap.GetCount(); +} + +void SharedDomain::AddShareableAssembly(Assembly * pAssembly) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + // We have a lock on the file. There should be no races to add the same assembly. + + { + LockHolder holder(this); + + EX_TRY + { + pAssembly->SetIsTenured(); + m_assemblyMap.InsertValue(pAssembly->HashIdentity(), pAssembly); + } + EX_HOOK + { + // There was an error adding the assembly to the assembly hash (probably an OOM), + // so we need to unset the tenured bit so that correct cleanup can happen. + pAssembly->UnsetIsTenured(); + } + EX_END_HOOK + } + + LOG((LF_CODESHARING, + LL_INFO100, + "Successfully added shareable assembly \"%s\".\n", + pAssembly->GetManifestFile()->GetSimpleName())); +} + +#endif // FEATURE_LOADER_OPTIMIZATION +#endif // !DACCESS_COMPILE + +DWORD DomainLocalModule::GetClassFlags(MethodTable* pMT, DWORD iClassIndex /*=(DWORD)-1*/) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + } CONTRACTL_END; + + { // SO tolerance exception for debug-only assertion. + CONTRACT_VIOLATION(SOToleranceViolation); + CONSISTENCY_CHECK(GetDomainFile()->GetModule() == pMT->GetModuleForStatics()); + } + + if (pMT->IsDynamicStatics()) + { + _ASSERTE(!pMT->ContainsGenericVariables()); + DWORD dynamicClassID = pMT->GetModuleDynamicEntryID(); + if(m_aDynamicEntries <= dynamicClassID) + return FALSE; + return (m_pDynamicClassTable[dynamicClassID].m_dwFlags); + } + else + { + if (iClassIndex == (DWORD)-1) + iClassIndex = pMT->GetClassIndex(); + return GetPrecomputedStaticsClassData()[iClassIndex]; + } +} + +#ifndef DACCESS_COMPILE + +void DomainLocalModule::SetClassInitialized(MethodTable* pMT) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + BaseDomain::DomainLocalBlockLockHolder lh(GetDomainFile()->GetAppDomain()); + + _ASSERTE(!IsClassInitialized(pMT)); + _ASSERTE(!IsClassInitError(pMT)); + + SetClassFlags(pMT, ClassInitFlags::INITIALIZED_FLAG); +} + +void DomainLocalModule::SetClassInitError(MethodTable* pMT) +{ + WRAPPER_NO_CONTRACT; + + BaseDomain::DomainLocalBlockLockHolder lh(GetDomainFile()->GetAppDomain()); + + SetClassFlags(pMT, ClassInitFlags::ERROR_FLAG); +} + +void DomainLocalModule::SetClassFlags(MethodTable* pMT, DWORD dwFlags) +{ + CONTRACTL { + THROWS; + GC_TRIGGERS; + PRECONDITION(GetDomainFile()->GetModule() == pMT->GetModuleForStatics()); + // Assumes BaseDomain::DomainLocalBlockLockHolder is taken + PRECONDITION(GetDomainFile()->GetAppDomain()->OwnDomainLocalBlockLock()); + } CONTRACTL_END; + + if (pMT->IsDynamicStatics()) + { + _ASSERTE(!pMT->ContainsGenericVariables()); + DWORD dwID = pMT->GetModuleDynamicEntryID(); + EnsureDynamicClassIndex(dwID); + m_pDynamicClassTable[dwID].m_dwFlags |= dwFlags; + } + else + { + GetPrecomputedStaticsClassData()[pMT->GetClassIndex()] |= dwFlags; + } +} + +void DomainLocalModule::EnsureDynamicClassIndex(DWORD dwID) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + // Assumes BaseDomain::DomainLocalBlockLockHolder is taken + PRECONDITION(GetDomainFile()->GetAppDomain()->OwnDomainLocalBlockLock()); + } + CONTRACTL_END; + + if (dwID < m_aDynamicEntries) + { + _ASSERTE(m_pDynamicClassTable.Load() != NULL); + return; + } + + SIZE_T aDynamicEntries = max(16, m_aDynamicEntries.Load()); + while (aDynamicEntries <= dwID) + { + aDynamicEntries *= 2; + } + + DynamicClassInfo* pNewDynamicClassTable; + pNewDynamicClassTable = (DynamicClassInfo*) + (void*)GetDomainFile()->GetLoaderAllocator()->GetHighFrequencyHeap()->AllocMem( + S_SIZE_T(sizeof(DynamicClassInfo)) * S_SIZE_T(aDynamicEntries)); + + memcpy(pNewDynamicClassTable, m_pDynamicClassTable, sizeof(DynamicClassInfo) * m_aDynamicEntries); + + // Note: Memory allocated on loader heap is zero filled + // memset(pNewDynamicClassTable + m_aDynamicEntries, 0, (aDynamicEntries - m_aDynamicEntries) * sizeof(DynamicClassInfo)); + + _ASSERTE(m_aDynamicEntries%2 == 0); + + // Commit new dynamic table. The lock-free helpers depend on the order. + MemoryBarrier(); + m_pDynamicClassTable = pNewDynamicClassTable; + MemoryBarrier(); + m_aDynamicEntries = aDynamicEntries; +} + +#ifndef CROSSGEN_COMPILE +void DomainLocalModule::AllocateDynamicClass(MethodTable *pMT) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + // Assumes BaseDomain::DomainLocalBlockLockHolder is taken + PRECONDITION(GetDomainFile()->GetAppDomain()->OwnDomainLocalBlockLock()); + } + CONTRACTL_END; + + _ASSERTE(!pMT->ContainsGenericVariables()); + _ASSERTE(!pMT->IsSharedByGenericInstantiations()); + _ASSERTE(GetDomainFile()->GetModule() == pMT->GetModuleForStatics()); + _ASSERTE(pMT->IsDynamicStatics()); + + DWORD dynamicEntryIDIndex = pMT->GetModuleDynamicEntryID(); + + EnsureDynamicClassIndex(dynamicEntryIDIndex); + + _ASSERTE(m_aDynamicEntries > dynamicEntryIDIndex); + + EEClass *pClass = pMT->GetClass(); + + DWORD dwStaticBytes = pClass->GetNonGCRegularStaticFieldBytes(); + DWORD dwNumHandleStatics = pClass->GetNumHandleRegularStatics(); + + _ASSERTE(!IsClassAllocated(pMT)); + _ASSERTE(!IsClassInitialized(pMT)); + _ASSERTE(!IsClassInitError(pMT)); + + DynamicEntry *pDynamicStatics = m_pDynamicClassTable[dynamicEntryIDIndex].m_pDynamicEntry; + + // We need this check because maybe a class had a cctor but no statics + if (dwStaticBytes > 0 || dwNumHandleStatics > 0) + { + if (pDynamicStatics == NULL) + { + LoaderHeap * pLoaderAllocator = GetDomainFile()->GetLoaderAllocator()->GetHighFrequencyHeap(); + + if (pMT->Collectible()) + { + pDynamicStatics = (DynamicEntry*)(void*)pLoaderAllocator->AllocMem(S_SIZE_T(sizeof(CollectibleDynamicEntry))); + } + else + { + SIZE_T dynamicEntrySize = DynamicEntry::GetOffsetOfDataBlob() + dwStaticBytes; + +#ifdef FEATURE_64BIT_ALIGNMENT + // Allocate memory with extra alignment only if it is really necessary + if (dwStaticBytes >= MAX_PRIMITIVE_FIELD_SIZE) + { + static_assert_no_msg(sizeof(NormalDynamicEntry) % MAX_PRIMITIVE_FIELD_SIZE == 0); + pDynamicStatics = (DynamicEntry*)(void*)pLoaderAllocator->AllocAlignedMem(dynamicEntrySize, MAX_PRIMITIVE_FIELD_SIZE); + } + else +#endif + pDynamicStatics = (DynamicEntry*)(void*)pLoaderAllocator->AllocMem(S_SIZE_T(dynamicEntrySize)); + } + + // Note: Memory allocated on loader heap is zero filled + + m_pDynamicClassTable[dynamicEntryIDIndex].m_pDynamicEntry = pDynamicStatics; + } + + if (pMT->Collectible() && (dwStaticBytes != 0)) + { + GCX_COOP(); + OBJECTREF nongcStaticsArray = NULL; + GCPROTECT_BEGIN(nongcStaticsArray); +#ifdef FEATURE_64BIT_ALIGNMENT + // Allocate memory with extra alignment only if it is really necessary + if (dwStaticBytes >= MAX_PRIMITIVE_FIELD_SIZE) + nongcStaticsArray = AllocatePrimitiveArray(ELEMENT_TYPE_I8, (dwStaticBytes + (sizeof(CLR_I8)-1)) / (sizeof(CLR_I8))); + else +#endif + nongcStaticsArray = AllocatePrimitiveArray(ELEMENT_TYPE_U1, dwStaticBytes); + ((CollectibleDynamicEntry *)pDynamicStatics)->m_hNonGCStatics = GetDomainFile()->GetModule()->GetLoaderAllocator()->AllocateHandle(nongcStaticsArray); + GCPROTECT_END(); + } + if (dwNumHandleStatics > 0) + { + if (!pMT->Collectible()) + { + GetAppDomain()->AllocateStaticFieldObjRefPtrs(dwNumHandleStatics, + &((NormalDynamicEntry *)pDynamicStatics)->m_pGCStatics); + } + else + { + GCX_COOP(); + OBJECTREF gcStaticsArray = NULL; + GCPROTECT_BEGIN(gcStaticsArray); + gcStaticsArray = AllocateObjectArray(dwNumHandleStatics, g_pObjectClass); + ((CollectibleDynamicEntry *)pDynamicStatics)->m_hGCStatics = GetDomainFile()->GetModule()->GetLoaderAllocator()->AllocateHandle(gcStaticsArray); + GCPROTECT_END(); + } + } + } +} + + +void DomainLocalModule::PopulateClass(MethodTable *pMT) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + } + CONTRACTL_END; + + _ASSERTE(!pMT->ContainsGenericVariables()); + + // the only work actually done here for non-dynamics is the freezing related work. + // See if we can eliminate this and make this a dynamic-only path + DWORD iClassIndex = pMT->GetClassIndex(); + + if (!IsClassAllocated(pMT, iClassIndex)) + { + BaseDomain::DomainLocalBlockLockHolder lh(GetDomainFile()->GetAppDomain()); + + if (!IsClassAllocated(pMT, iClassIndex)) + { + // Allocate dynamic space if necessary + if (pMT->IsDynamicStatics()) + AllocateDynamicClass(pMT); + + // determine flags to set on the statics block + DWORD dwFlags = ClassInitFlags::ALLOCATECLASS_FLAG; + + if (!pMT->HasClassConstructor() && !pMT->HasBoxedRegularStatics()) + { + _ASSERTE(!IsClassInitialized(pMT)); + _ASSERTE(!IsClassInitError(pMT)); + dwFlags |= ClassInitFlags::INITIALIZED_FLAG; + } + + if (pMT->Collectible()) + { + dwFlags |= ClassInitFlags::COLLECTIBLE_FLAG; + } + + // Set all flags at the same time to avoid races + SetClassFlags(pMT, dwFlags); + } + } + + return; +} +#endif // CROSSGEN_COMPILE + +void DomainLocalBlock::EnsureModuleIndex(ModuleIndex index) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + // Assumes BaseDomain::DomainLocalBlockLockHolder is taken + PRECONDITION(m_pDomain->OwnDomainLocalBlockLock()); + } + CONTRACTL_END; + + if (m_aModuleIndices > index.m_dwIndex) + { + _ASSERTE(m_pModuleSlots != NULL); + return; + } + + SIZE_T aModuleIndices = max(16, m_aModuleIndices); + while (aModuleIndices <= index.m_dwIndex) + { + aModuleIndices *= 2; + } + + PTR_DomainLocalModule* pNewModuleSlots = (PTR_DomainLocalModule*) (void*)m_pDomain->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(PTR_DomainLocalModule)) * S_SIZE_T(aModuleIndices)); + + memcpy(pNewModuleSlots, m_pModuleSlots, sizeof(SIZE_T)*m_aModuleIndices); + + // Note: Memory allocated on loader heap is zero filled + // memset(pNewModuleSlots + m_aModuleIndices, 0 , (aModuleIndices - m_aModuleIndices)*sizeof(PTR_DomainLocalModule) ); + + // Commit new table. The lock-free helpers depend on the order. + MemoryBarrier(); + m_pModuleSlots = pNewModuleSlots; + MemoryBarrier(); + m_aModuleIndices = aModuleIndices; + +} + +void DomainLocalBlock::SetModuleSlot(ModuleIndex index, PTR_DomainLocalModule pLocalModule) +{ + // Need to synchronize with table growth in this domain + BaseDomain::DomainLocalBlockLockHolder lh(m_pDomain); + + EnsureModuleIndex(index); + + _ASSERTE(index.m_dwIndex < m_aModuleIndices); + + // We would like this assert here, unfortunately, loading a module in this appdomain can fail + // after here and we will keep the module around and reuse the slot when we retry (if + // the failure happened due to a transient error, such as OOM). In that case the slot wont + // be null. + //_ASSERTE(m_pModuleSlots[index.m_dwIndex] == 0); + + m_pModuleSlots[index.m_dwIndex] = pLocalModule; +} + +#ifndef CROSSGEN_COMPILE + +DomainAssembly* AppDomain::RaiseTypeResolveEventThrowing(DomainAssembly* pAssembly, LPCSTR szName, ASSEMBLYREF *pResultingAssemblyRef) +{ + CONTRACTL + { + MODE_ANY; + GC_TRIGGERS; + THROWS; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + OVERRIDE_TYPE_LOAD_LEVEL_LIMIT(CLASS_LOADED); + + + DomainAssembly* pResolvedAssembly = NULL; + _ASSERTE(strcmp(szName, g_AppDomainClassName)); + + GCX_COOP(); + + struct _gc { + OBJECTREF AppDomainRef; + OBJECTREF AssemblyRef; + STRINGREF str; + } gc; + ZeroMemory(&gc, sizeof(gc)); + + GCPROTECT_BEGIN(gc); + if ((gc.AppDomainRef = GetRawExposedObject()) != NULL) + { + if (pAssembly != NULL) + gc.AssemblyRef = pAssembly->GetExposedAssemblyObject(); + + MethodDescCallSite onTypeResolve(METHOD__APP_DOMAIN__ON_TYPE_RESOLVE, &gc.AppDomainRef); + + gc.str = StringObject::NewString(szName); + ARG_SLOT args[3] = + { + ObjToArgSlot(gc.AppDomainRef), + ObjToArgSlot(gc.AssemblyRef), + ObjToArgSlot(gc.str) + }; + ASSEMBLYREF ResultingAssemblyRef = (ASSEMBLYREF) onTypeResolve.Call_RetOBJECTREF(args); + + if (ResultingAssemblyRef != NULL) + { + pResolvedAssembly = ResultingAssemblyRef->GetDomainAssembly(); + + if (pResultingAssemblyRef) + *pResultingAssemblyRef = ResultingAssemblyRef; + else + { + if (pResolvedAssembly->IsCollectible()) + { + COMPlusThrow(kNotSupportedException, W("NotSupported_CollectibleBoundNonCollectible")); + } + } + } + } + GCPROTECT_END(); + + return pResolvedAssembly; +} + + +Assembly* AppDomain::RaiseResourceResolveEvent(DomainAssembly* pAssembly, LPCSTR szName) +{ + CONTRACT(Assembly*) + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACT_END; + + Assembly* pResolvedAssembly = NULL; + + GCX_COOP(); + + struct _gc { + OBJECTREF AppDomainRef; + OBJECTREF AssemblyRef; + STRINGREF str; + } gc; + ZeroMemory(&gc, sizeof(gc)); + + GCPROTECT_BEGIN(gc); + if ((gc.AppDomainRef = GetRawExposedObject()) != NULL) + { + if (pAssembly != NULL) + gc.AssemblyRef=pAssembly->GetExposedAssemblyObject(); + + MethodDescCallSite onResourceResolve(METHOD__APP_DOMAIN__ON_RESOURCE_RESOLVE, &gc.AppDomainRef); + gc.str = StringObject::NewString(szName); + ARG_SLOT args[3] = + { + ObjToArgSlot(gc.AppDomainRef), + ObjToArgSlot(gc.AssemblyRef), + ObjToArgSlot(gc.str) + }; + ASSEMBLYREF ResultingAssemblyRef = (ASSEMBLYREF) onResourceResolve.Call_RetOBJECTREF(args); + if (ResultingAssemblyRef != NULL) + { + pResolvedAssembly = ResultingAssemblyRef->GetAssembly(); + if (pResolvedAssembly->IsCollectible()) + { + COMPlusThrow(kNotSupportedException, W("NotSupported_CollectibleAssemblyResolve")); + } + } + } + GCPROTECT_END(); + + RETURN pResolvedAssembly; +} + + +Assembly * +AppDomain::RaiseAssemblyResolveEvent( + AssemblySpec * pSpec, + BOOL fIntrospection, + BOOL fPreBind) +{ + CONTRACT(Assembly*) + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACT_END; + + BinderMethodID methodId; + StackSString ssName; + pSpec->GetFileOrDisplayName(0, ssName); + +#ifdef FEATURE_REFLECTION_ONLY_LOAD + if ( (!fIntrospection) && (!fPreBind) ) + { + methodId = METHOD__APP_DOMAIN__ON_ASSEMBLY_RESOLVE; // post-bind execution event (the classic V1.0 event) + } + else if ((!fIntrospection) && fPreBind) + { + RETURN NULL; // There is currently no prebind execution resolve event + } + else if (fIntrospection && !fPreBind) + { + RETURN NULL; // There is currently no post-bind introspection resolve event + } + else + { + _ASSERTE( fIntrospection && fPreBind ); + methodId = METHOD__APP_DOMAIN__ON_REFLECTION_ONLY_ASSEMBLY_RESOLVE; // event for introspection assemblies + } +#else // FEATURE_REFLECTION_ONLY_LOAD + if (!fPreBind) + { + methodId = METHOD__APP_DOMAIN__ON_ASSEMBLY_RESOLVE; // post-bind execution event (the classic V1.0 event) + } + else + { + RETURN NULL; + } + +#endif // FEATURE_REFLECTION_ONLY_LOAD + + // Elevate threads allowed loading level. This allows the host to load an assembly even in a restricted + // condition. Note, however, that this exposes us to possible recursion failures, if the host tries to + // load the assemblies currently being loaded. (Such cases would then throw an exception.) + + OVERRIDE_LOAD_LEVEL_LIMIT(FILE_ACTIVE); + OVERRIDE_TYPE_LOAD_LEVEL_LIMIT(CLASS_LOADED); + + GCX_COOP(); + + Assembly* pAssembly = NULL; + + struct _gc { + OBJECTREF AppDomainRef; + OBJECTREF AssemblyRef; + STRINGREF str; + } gc; + ZeroMemory(&gc, sizeof(gc)); + + GCPROTECT_BEGIN(gc); + if ((gc.AppDomainRef = GetRawExposedObject()) != NULL) + { + if (pSpec->GetParentAssembly() != NULL) + { + if ( pSpec->IsIntrospectionOnly() +#ifdef FEATURE_FUSION + || pSpec->GetParentLoadContext() == LOADCTX_TYPE_UNKNOWN +#endif + ) + { + gc.AssemblyRef=pSpec->GetParentAssembly()->GetExposedAssemblyObject(); + } + } + MethodDescCallSite onAssemblyResolve(methodId, &gc.AppDomainRef); + + gc.str = StringObject::NewString(ssName); + ARG_SLOT args[3] = { + ObjToArgSlot(gc.AppDomainRef), + ObjToArgSlot(gc.AssemblyRef), + ObjToArgSlot(gc.str) + }; + + ASSEMBLYREF ResultingAssemblyRef = (ASSEMBLYREF) onAssemblyResolve.Call_RetOBJECTREF(args); + + if (ResultingAssemblyRef != NULL) + { + pAssembly = ResultingAssemblyRef->GetAssembly(); + if (pAssembly->IsCollectible()) + { + COMPlusThrow(kNotSupportedException, W("NotSupported_CollectibleAssemblyResolve")); + } + } + } + GCPROTECT_END(); + + if (pAssembly != NULL) + { + if ((!(pAssembly->IsIntrospectionOnly())) != (!fIntrospection)) + { + // Cannot return an introspection assembly from an execution callback or vice-versa + COMPlusThrow(kFileLoadException, pAssembly->IsIntrospectionOnly() ? IDS_CLASSLOAD_ASSEMBLY_RESOLVE_RETURNED_INTROSPECTION : IDS_CLASSLOAD_ASSEMBLY_RESOLVE_RETURNED_EXECUTION); + } + + // Check that the public key token matches the one specified in the spec + // MatchPublicKeys throws as appropriate + pSpec->MatchPublicKeys(pAssembly); + } + + RETURN pAssembly; +} // AppDomain::RaiseAssemblyResolveEvent + +#ifndef FEATURE_CORECLR + +//--------------------------------------------------------------------------------------- +// +// Ask the AppDomainManager for the entry assembly of the application +// +// Note: +// Most AppDomainManagers will fall back on the root assembly for the domain, so we need +// to make sure this is set before we call through to the AppDomainManager itself. +// + +Assembly *AppDomain::GetAppDomainManagerEntryAssembly() +{ + CONTRACT(Assembly *) + { + STANDARD_VM_CHECK; + PRECONDITION(HasAppDomainManagerInfo()); + PRECONDITION(CheckPointer(m_pRootAssembly)); + POSTCONDITION(CheckPointer(RETVAL)); + } + CONTRACT_END; + + GCX_COOP(); + + Assembly *pEntryAssembly = NULL; + + struct + { + APPDOMAINREF orDomain; + OBJECTREF orAppDomainManager; + ASSEMBLYREF orEntryAssembly; + } + gc; + ZeroMemory(&gc, sizeof(gc)); + + GCPROTECT_BEGIN(gc); + + gc.orDomain = static_cast(GetExposedObject()); + gc.orAppDomainManager = gc.orDomain->GetAppDomainManager(); + _ASSERTE(gc.orAppDomainManager != NULL); + + MethodDescCallSite getEntryAssembly(METHOD__APPDOMAIN_MANAGER__GET_ENTRY_ASSEMBLY, &gc.orAppDomainManager); + ARG_SLOT argThis = ObjToArgSlot(gc.orAppDomainManager); + gc.orEntryAssembly = static_cast(getEntryAssembly.Call_RetOBJECTREF(&argThis)); + + if (gc.orEntryAssembly != NULL) + { + pEntryAssembly = gc.orEntryAssembly->GetAssembly(); + } + + GCPROTECT_END(); + + // If the AppDomainManager did not return an entry assembly, we'll assume the default assembly + if (pEntryAssembly == NULL) + { + pEntryAssembly = m_pRootAssembly; + } + + RETURN(pEntryAssembly); +} + +#endif // !FEATURE_CORECLR + +//--------------------------------------------------------------------------------------- +// +// Determine the type of AppDomainManager to use for the default AppDomain +// +// Notes: +// v2.0 of the CLR used environment variables APPDOMAIN_MANAGER_ASM and APPDOMAIN_MANAGER_TYPE to set the +// domain manager. For compatibility these are still supported, along with appDomainManagerAsm and +// appDomainManagerType config file switches. If the config switches are supplied, the entry point must be +// fully trusted. +// + +void AppDomain::InitializeDefaultDomainManager() +{ + CONTRACTL + { + MODE_COOPERATIVE; + GC_TRIGGERS; + THROWS; + INJECT_FAULT(COMPlusThrowOM();); + PRECONDITION(GetId().m_dwId == DefaultADID); + PRECONDITION(!HasAppDomainManagerInfo()); + } + CONTRACTL_END; + + // + // The AppDomainManager for the default domain can be specified by: + // 1. Native hosting API + // 2. Application config file if the application is fully trusted + // 3. Environment variables + // + + + if (CorHost2::HasAppDomainManagerInfo()) + { + SetAppDomainManagerInfo(CorHost2::GetAppDomainManagerAsm(), + CorHost2::GetAppDomainManagerType(), + CorHost2::GetAppDomainManagerInitializeNewDomainFlags()); + m_fAppDomainManagerSetInConfig = FALSE; + + LOG((LF_APPDOMAIN, LL_INFO10, "Setting default AppDomainManager '%S', '%S' from hosting API.\n", GetAppDomainManagerAsm(), GetAppDomainManagerType())); + } +#ifndef FEATURE_CORECLR + else + { + CLRConfigStringHolder wszConfigAppDomainManagerAssembly(CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_AppDomainManagerAsm)); + CLRConfigStringHolder wszConfigAppDomainManagerType(CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_AppDomainManagerType)); + + if (wszConfigAppDomainManagerAssembly != NULL && + wszConfigAppDomainManagerType != NULL) + { + SetAppDomainManagerInfo(wszConfigAppDomainManagerAssembly, + wszConfigAppDomainManagerType, + eInitializeNewDomainFlags_None); + m_fAppDomainManagerSetInConfig = TRUE; + + LOG((LF_APPDOMAIN, LL_INFO10, "Setting default AppDomainManager '%S', '%S' from application config file.\n", GetAppDomainManagerAsm(), GetAppDomainManagerType())); + } + else + { + CLRConfigStringHolder wszEnvironmentAppDomainManagerAssembly(CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_LEGACY_APPDOMAIN_MANAGER_ASM)); + CLRConfigStringHolder wszEnvironmentAppDomainManagerType(CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_LEGACY_APPDOMAIN_MANAGER_TYPE)); + + if (wszEnvironmentAppDomainManagerAssembly != NULL && + wszEnvironmentAppDomainManagerType != NULL) + { + SetAppDomainManagerInfo(wszEnvironmentAppDomainManagerAssembly, + wszEnvironmentAppDomainManagerType, + eInitializeNewDomainFlags_None); + m_fAppDomainManagerSetInConfig = FALSE; + + LOG((LF_APPDOMAIN, LL_INFO10, "Setting default AppDomainManager '%S', '%S' from environment variables.\n", GetAppDomainManagerAsm(), GetAppDomainManagerType())); + + // Reset the environmetn variables so that child processes do not inherit our domain manager + // by default. + WszSetEnvironmentVariable(CLRConfig::EXTERNAL_LEGACY_APPDOMAIN_MANAGER_ASM.name, NULL); + WszSetEnvironmentVariable(CLRConfig::EXTERNAL_LEGACY_APPDOMAIN_MANAGER_TYPE.name, NULL); + } + } + } +#endif // !FEATURE_CORECLR + + // If we found an AppDomain manager to use, create and initialize it + // Otherwise, initialize the config flags. + if (HasAppDomainManagerInfo()) + { + // If the initialization flags promise that the domain manager isn't going to modify security, then do a + // pre-resolution of the domain now so that we can do some basic verification of the state later. We + // don't care about the actual result now, just that the resolution took place to compare against later. + if (GetAppDomainManagerInitializeNewDomainFlags() & eInitializeNewDomainFlags_NoSecurityChanges) + { + BOOL fIsFullyTrusted; + BOOL fIsHomogeneous; + GetSecurityDescriptor()->PreResolve(&fIsFullyTrusted, &fIsHomogeneous); + } + + OBJECTREF orThis = GetExposedObject(); + GCPROTECT_BEGIN(orThis); + + MethodDescCallSite createDomainManager(METHOD__APP_DOMAIN__CREATE_APP_DOMAIN_MANAGER); + ARG_SLOT args[] = + { + ObjToArgSlot(orThis) + }; + + createDomainManager.Call(args); + + GCPROTECT_END(); + } + else + { + OBJECTREF orThis = GetExposedObject(); + GCPROTECT_BEGIN(orThis); + + MethodDescCallSite initCompatFlags(METHOD__APP_DOMAIN__INITIALIZE_COMPATIBILITY_FLAGS); + ARG_SLOT args[] = + { + ObjToArgSlot(orThis) + }; + + initCompatFlags.Call(args); + + GCPROTECT_END(); + } +} + +#ifdef FEATURE_CLICKONCE + +//--------------------------------------------------------------------------------------- +// +// If we are launching a ClickOnce application, setup the default domain with the deails +// of the application. +// + +void AppDomain::InitializeDefaultClickOnceDomain() +{ + CONTRACTL + { + MODE_COOPERATIVE; + GC_TRIGGERS; + THROWS; + PRECONDITION(GetId().m_dwId == DefaultADID); + } + CONTRACTL_END; + + // + // If the CLR is being started to run a ClickOnce application, then capture the information about the + // application to setup the default domain wtih. + // + + if (CorCommandLine::m_pwszAppFullName != NULL) + { + struct + { + OBJECTREF orThis; + STRINGREF orAppFullName; + PTRARRAYREF orManifestPathsArray; + PTRARRAYREF orActivationDataArray; + } + gc; + ZeroMemory(&gc, sizeof(gc)); + + GCPROTECT_BEGIN(gc); + + gc.orAppFullName = StringObject::NewString(CorCommandLine::m_pwszAppFullName); + + // If specific manifests have been pointed at, make a note of them + if (CorCommandLine::m_dwManifestPaths > 0) + { + _ASSERTE(CorCommandLine::m_ppwszManifestPaths != NULL); + + gc.orManifestPathsArray = static_cast(AllocateObjectArray(CorCommandLine::m_dwManifestPaths, g_pStringClass)); + for (DWORD i = 0; i < CorCommandLine::m_dwManifestPaths; ++i) + { + STRINGREF str = StringObject::NewString(CorCommandLine::m_ppwszManifestPaths[i]); + gc.orManifestPathsArray->SetAt(i, str); + } + } + + // Check for any activation parameters to pass to the ClickOnce application + if (CorCommandLine::m_dwActivationData > 0) + { + _ASSERTE(CorCommandLine::m_ppwszActivationData != NULL); + + gc.orActivationDataArray = static_cast(AllocateObjectArray(CorCommandLine::m_dwActivationData, g_pStringClass)); + for (DWORD i = 0; i < CorCommandLine::m_dwActivationData; ++i) + { + STRINGREF str = StringObject::NewString(CorCommandLine::m_ppwszActivationData[i]); + gc.orActivationDataArray->SetAt(i, str); + } + } + + gc.orThis = GetExposedObject(); + + MethodDescCallSite setupDefaultClickOnceDomain(METHOD__APP_DOMAIN__SETUP_DEFAULT_CLICKONCE_DOMAIN); + ARG_SLOT args[] = + { + ObjToArgSlot(gc.orThis), + ObjToArgSlot(gc.orAppFullName), + ObjToArgSlot(gc.orManifestPathsArray), + ObjToArgSlot(gc.orActivationDataArray), + }; + setupDefaultClickOnceDomain.Call(args); + + GCPROTECT_END(); + } +} + +BOOL AppDomain::IsClickOnceAppDomain() +{ + CONTRACTL + { + MODE_COOPERATIVE; + GC_TRIGGERS; + THROWS; + } + CONTRACTL_END; + + return ((APPDOMAINREF)GetExposedObject())->HasActivationContext(); +} + +#endif // FEATURE_CLICKONCE + +//--------------------------------------------------------------------------------------- +// +// Intialize the security settings in the default AppDomain. +// + +void AppDomain::InitializeDefaultDomainSecurity() +{ + CONTRACTL + { + MODE_COOPERATIVE; + GC_TRIGGERS; + THROWS; + PRECONDITION(GetId().m_dwId == DefaultADID); + } + CONTRACTL_END; + + OBJECTREF orThis = GetExposedObject(); + GCPROTECT_BEGIN(orThis); + + MethodDescCallSite initializeSecurity(METHOD__APP_DOMAIN__INITIALIZE_DOMAIN_SECURITY); + ARG_SLOT args[] = + { + ObjToArgSlot(orThis), + ObjToArgSlot(NULL), + ObjToArgSlot(NULL), + static_cast(FALSE), + ObjToArgSlot(NULL), + static_cast(FALSE) + }; + + initializeSecurity.Call(args); + + GCPROTECT_END(); +} + +CLREvent * AppDomain::g_pUnloadStartEvent; + +void AppDomain::CreateADUnloadWorker() +{ + STANDARD_VM_CONTRACT; + +#ifdef FEATURE_CORECLR + // Do not create adUnload thread if there is only default domain + if(IsSingleAppDomain()) + return; +#endif + +Retry: + BOOL fCreator = FALSE; + if (FastInterlockCompareExchange((LONG *)&g_fADUnloadWorkerOK,-2,-1)==-1) //we're first + { +#ifdef _TARGET_X86_ // use the smallest possible stack on X86 + DWORD stackSize = 128 * 1024; +#else + DWORD stackSize = 512 * 1024; // leave X64 unchanged since we have plenty of VM +#endif + Thread *pThread = SetupUnstartedThread(); + if (pThread->CreateNewThread(stackSize, ADUnloadThreadStart, pThread)) + { + fCreator = TRUE; + DWORD dwRet; + dwRet = pThread->StartThread(); + + // When running under a user mode native debugger there is a race + // between the moment we've created the thread (in CreateNewThread) and + // the moment we resume it (in StartThread); the debugger may receive + // the "ct" (create thread) notification, and it will attempt to + // suspend/resume all threads in the process. Now imagine the debugger + // resumes this thread first, and only later does it try to resume the + // newly created thread (the ADU worker thread). In these conditions our + // call to ResumeThread may come before the debugger's call to ResumeThread + // actually causing dwRet to equal 2. + // We cannot use IsDebuggerPresent() in the condition below because the + // debugger may have been detached between the time it got the notification + // and the moment we execute the test below. + _ASSERTE(dwRet == 1 || dwRet == 2); + } + else + { + pThread->DecExternalCount(FALSE); + FastInterlockExchange((LONG *)&g_fADUnloadWorkerOK, -1); + ThrowOutOfMemory(); + } + } + + YIELD_WHILE (g_fADUnloadWorkerOK == -2); + + if (g_fADUnloadWorkerOK == -1) { + if (fCreator) + { + ThrowOutOfMemory(); + } + else + { + goto Retry; + } + } +} + +/*static*/ void AppDomain::ADUnloadWorkerHelper(AppDomain *pDomain) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_COOPERATIVE; + ADUnloadSink* pADUnloadSink=pDomain->GetADUnloadSinkForUnload(); + HRESULT hr=S_OK; + + EX_TRY + { + pDomain->Unload(FALSE); + } + EX_CATCH_HRESULT(hr); + + if(pADUnloadSink) + { + SystemDomain::LockHolder lh; + pADUnloadSink->ReportUnloadResult(hr,NULL); + pADUnloadSink->Release(); + } +} + +void AppDomain::DoADUnloadWork() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + DWORD i = 1; + while (TRUE) { + + AppDomain *pDomainToUnload = NULL; + + { + // Take the lock so that no domain can be added or removed from the system domain + SystemDomain::LockHolder lh; + + DWORD numDomain = SystemDomain::GetCurrentAppDomainMaxIndex(); + for (; i <= numDomain; i ++) { + AppDomain * pDomain = SystemDomain::TestGetAppDomainAtIndex(ADIndex(i)); + // + // @todo: We used to also select a domain if pDomain->IsUnload() returned true. But that causes + // problems when we've failed to completely unload the AD in the past. If we've reached the CLEARED + // stage, for instance, then there will be no default context and AppDomain::Exit() will simply crash. + // + if (pDomain && pDomain->IsUnloadRequested()) + { + pDomainToUnload = pDomain; + i ++; + break; + } + } + } + + if (!pDomainToUnload) { + break; + } + + // We are the only thread that can unload domains so no one else can delete the appdomain + ADUnloadWorkerHelper(pDomainToUnload); + } +} + +static void DoADUnloadWorkHelper() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_COOPERATIVE; + + EX_TRY { + AppDomain::DoADUnloadWork(); + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions); +} + +ULONGLONG g_ObjFinalizeStartTime = 0; +Volatile g_FinalizerIsRunning = FALSE; +Volatile g_FinalizerLoopCount = 0; + +ULONGLONG GetObjFinalizeStartTime() +{ + LIMITED_METHOD_CONTRACT; + return g_ObjFinalizeStartTime; +} + +void FinalizerThreadAbortOnTimeout() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_MODE_COOPERATIVE; + STATIC_CONTRACT_GC_TRIGGERS; + + { + // If finalizer thread is blocked because scheduler is running another task, + // or it is waiting for another thread, we first see if we get finalizer thread + // running again. + Thread::ThreadAbortWatchDog(); + } + + EX_TRY + { + Thread *pFinalizerThread = FinalizerThread::GetFinalizerThread(); + EPolicyAction action = GetEEPolicy()->GetActionOnTimeout(OPR_FinalizerRun, pFinalizerThread); + switch (action) + { + case eAbortThread: + GetEEPolicy()->NotifyHostOnTimeout(OPR_FinalizerRun, action); + pFinalizerThread->UserAbort(Thread::TAR_Thread, + EEPolicy::TA_Safe, + INFINITE, + Thread::UAC_FinalizerTimeout); + break; + case eRudeAbortThread: + GetEEPolicy()->NotifyHostOnTimeout(OPR_FinalizerRun, action); + pFinalizerThread->UserAbort(Thread::TAR_Thread, + EEPolicy::TA_Rude, + INFINITE, + Thread::UAC_FinalizerTimeout); + break; + case eUnloadAppDomain: + { + AppDomain *pDomain = pFinalizerThread->GetDomain(); + pFinalizerThread->UserAbort(Thread::TAR_Thread, + EEPolicy::TA_Safe, + INFINITE, + Thread::UAC_FinalizerTimeout); + if (!pDomain->IsDefaultDomain()) + { + GetEEPolicy()->NotifyHostOnTimeout(OPR_FinalizerRun, action); + pDomain->EnableADUnloadWorker(EEPolicy::ADU_Safe); + } + } + break; + case eRudeUnloadAppDomain: + { + AppDomain *pDomain = pFinalizerThread->GetDomain(); + pFinalizerThread->UserAbort(Thread::TAR_Thread, + EEPolicy::TA_Rude, + INFINITE, + Thread::UAC_FinalizerTimeout); + if (!pDomain->IsDefaultDomain()) + { + GetEEPolicy()->NotifyHostOnTimeout(OPR_FinalizerRun, action); + pDomain->EnableADUnloadWorker(EEPolicy::ADU_Rude); + } + } + break; + case eExitProcess: + case eFastExitProcess: + case eRudeExitProcess: + case eDisableRuntime: + GetEEPolicy()->NotifyHostOnTimeout(OPR_FinalizerRun, action); + EEPolicy::HandleExitProcessFromEscalation(action, HOST_E_EXITPROCESS_TIMEOUT); + _ASSERTE (!"Should not get here"); + break; + default: + break; + } + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions); +} + +enum WorkType +{ + WT_UnloadDomain = 0x1, + WT_ThreadAbort = 0x2, + WT_FinalizerThread = 0x4, + WT_ClearCollectedDomains=0x8 +}; + +static Volatile s_WorkType = 0; + + +DWORD WINAPI AppDomain::ADUnloadThreadStart(void *args) +{ + CONTRACTL + { + NOTHROW; + DISABLED(GC_TRIGGERS); + + // This function will always be at the very bottom of the stack. The only + // user code it calls is the AppDomainUnload notifications which we will + // not be hardenning for Whidbey. + // + ENTRY_POINT; + } + CONTRACTL_END; + + BEGIN_ENTRYPOINT_NOTHROW; + + ClrFlsSetThreadType (ThreadType_ADUnloadHelper); + + Thread *pThread = (Thread*)args; + bool fOK = (pThread->HasStarted() != 0); + + { + GCX_MAYBE_PREEMP(fOK); + + if (fOK) + { + EX_TRY + { + if (CLRTaskHosted()) + { + // ADUnload helper thread is critical. We do not want it to share scheduler + // with other tasks. + pThread->LeaveRuntime(0); + } + } + EX_CATCH + { + fOK = false; + } + EX_END_CATCH(SwallowAllExceptions); + } + + _ASSERTE (g_fADUnloadWorkerOK == -2); + + FastInterlockExchange((LONG *)&g_fADUnloadWorkerOK,fOK?1:-1); + + if (!fOK) + { + DestroyThread(pThread); + goto Exit; + } + + pThread->SetBackground(TRUE); + + pThread->SetThreadStateNC(Thread::TSNC_ADUnloadHelper); + + while (TRUE) { + DWORD TAtimeout = INFINITE; + ULONGLONG endTime = Thread::GetNextSelfAbortEndTime(); + ULONGLONG curTime = CLRGetTickCount64(); + if (endTime <= curTime) { + TAtimeout = 5; + } + else + { + ULONGLONG diff = endTime - curTime; + if (diff < MAXULONG) + { + TAtimeout = (DWORD)diff; + } + } + ULONGLONG finalizeStartTime = GetObjFinalizeStartTime(); + DWORD finalizeTimeout = INFINITE; + DWORD finalizeTimeoutSetting = GetEEPolicy()->GetTimeout(OPR_FinalizerRun); + if (finalizeTimeoutSetting != INFINITE && g_FinalizerIsRunning) + { + if (finalizeStartTime == 0) + { + finalizeTimeout = finalizeTimeoutSetting; + } + else + { + endTime = finalizeStartTime + finalizeTimeoutSetting; + if (endTime <= curTime) { + finalizeTimeout = 0; + } + else + { + ULONGLONG diff = endTime - curTime; + if (diff < MAXULONG) + { + finalizeTimeout = (DWORD)diff; + } + } + } + } + + if (AppDomain::HasWorkForFinalizerThread()) + { + if (finalizeTimeout > finalizeTimeoutSetting) + { + finalizeTimeout = finalizeTimeoutSetting; + } + } + + DWORD timeout = INFINITE; + if (finalizeTimeout <= TAtimeout) + { + timeout = finalizeTimeout; + } + else + { + timeout = TAtimeout; + } + + if (timeout != 0) + { + LOG((LF_APPDOMAIN, LL_INFO10, "Waiting to start unload\n")); + g_pUnloadStartEvent->Wait(timeout,FALSE); + } + + if (finalizeTimeout != INFINITE || (s_WorkType & WT_FinalizerThread) != 0) + { + STRESS_LOG0(LF_ALWAYS, LL_ALWAYS, "ADUnloadThreadStart work for Finalizer thread\n"); + FastInterlockAnd(&s_WorkType, ~WT_FinalizerThread); + // only watch finalizer thread is finalizer method or unloadevent is being processed + if (GetObjFinalizeStartTime() == finalizeStartTime && finalizeStartTime != 0 && g_FinalizerIsRunning) + { + if (CLRGetTickCount64() >= finalizeStartTime+finalizeTimeoutSetting) + { + GCX_COOP(); + FinalizerThreadAbortOnTimeout(); + } + } + if (s_fProcessUnloadDomainEvent && g_FinalizerIsRunning) + { + GCX_COOP(); + FinalizerThreadAbortOnTimeout(); + } + } + + if (TAtimeout != INFINITE || (s_WorkType & WT_ThreadAbort) != 0) + { + STRESS_LOG0(LF_ALWAYS, LL_ALWAYS, "ADUnloadThreadStart work for thread abort\n"); + FastInterlockAnd(&s_WorkType, ~WT_ThreadAbort); + GCX_COOP(); + Thread::ThreadAbortWatchDog(); + } + + if ((s_WorkType & WT_UnloadDomain) != 0 && !AppDomain::HasWorkForFinalizerThread()) + { + STRESS_LOG0(LF_ALWAYS, LL_ALWAYS, "ADUnloadThreadStart work for AD unload\n"); + FastInterlockAnd(&s_WorkType, ~WT_UnloadDomain); + GCX_COOP(); + DoADUnloadWorkHelper(); + } + + if ((s_WorkType & WT_ClearCollectedDomains) != 0) + { + STRESS_LOG0(LF_ALWAYS, LL_ALWAYS, "ADUnloadThreadStart work for AD cleanup\n"); + FastInterlockAnd(&s_WorkType, ~WT_ClearCollectedDomains); + GCX_COOP(); + SystemDomain::System()->ClearCollectedDomains(); + } + + } +Exit:; + } + + END_ENTRYPOINT_NOTHROW; + + return 0; +} + +void AppDomain::EnableADUnloadWorker() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; // Called during a SO + } + CONTRACTL_END; + + EEPolicy::AppDomainUnloadTypes type = EEPolicy::ADU_Safe; + +#ifdef _DEBUG + DWORD hostTestADUnload = g_pConfig->GetHostTestADUnload(); + if (hostTestADUnload == 2) { + type = EEPolicy::ADU_Rude; + } +#endif // _DEBUG + + EnableADUnloadWorker(type); +} + +void AppDomain::EnableADUnloadWorker(EEPolicy::AppDomainUnloadTypes type, BOOL fHasStack) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; // Called during a SO + } + CONTRACTL_END; + + FastInterlockOr (&s_WorkType, WT_UnloadDomain); + + LONG stage = m_Stage; + static_assert_no_msg(sizeof(m_Stage) == sizeof(int)); + + _ASSERTE(!IsDefaultDomain()); + + // Mark unload requested. + if (type == EEPolicy::ADU_Rude) { + SetRudeUnload(); + } + while (stage < STAGE_UNLOAD_REQUESTED) { + stage = FastInterlockCompareExchange((LONG*)&m_Stage,STAGE_UNLOAD_REQUESTED,stage); + } + + if (!fHasStack) + { + // Can not call Set due to limited stack. + return; + } + LOG((LF_APPDOMAIN, LL_INFO10, "Enabling unload worker\n")); + g_pUnloadStartEvent->Set(); +} + +void AppDomain::EnableADUnloadWorkerForThreadAbort() +{ + LIMITED_METHOD_CONTRACT; + STRESS_LOG0(LF_ALWAYS, LL_ALWAYS, "Enabling unload worker for thread abort\n"); + LOG((LF_APPDOMAIN, LL_INFO10, "Enabling unload worker for thread abort\n")); + FastInterlockOr (&s_WorkType, WT_ThreadAbort); + g_pUnloadStartEvent->Set(); +} + + +void AppDomain::EnableADUnloadWorkerForFinalizer() +{ + LIMITED_METHOD_CONTRACT; + if (GetEEPolicy()->GetTimeout(OPR_FinalizerRun) != INFINITE) + { + LOG((LF_APPDOMAIN, LL_INFO10, "Enabling unload worker for Finalizer Thread\n")); + FastInterlockOr (&s_WorkType, WT_FinalizerThread); + g_pUnloadStartEvent->Set(); + } +} + +void AppDomain::EnableADUnloadWorkerForCollectedADCleanup() +{ + LIMITED_METHOD_CONTRACT; + LOG((LF_APPDOMAIN, LL_INFO10, "Enabling unload worker for collected domains\n")); + FastInterlockOr (&s_WorkType, WT_ClearCollectedDomains); + g_pUnloadStartEvent->Set(); +} + + +void SystemDomain::ClearCollectedDomains() +{ + CONTRACTL + { + GC_TRIGGERS; + NOTHROW; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + AppDomain* pDomainsToClear=NULL; + { + CrstHolder lh(&m_DelayedUnloadCrst); + for (AppDomain** ppDomain=&m_pDelayedUnloadList;(*ppDomain)!=NULL; ) + { + if ((*ppDomain)->m_Stage==AppDomain::STAGE_COLLECTED) + { + AppDomain* pAppDomain=*ppDomain; + *ppDomain=(*ppDomain)->m_pNextInDelayedUnloadList; + pAppDomain->m_pNextInDelayedUnloadList=pDomainsToClear; + pDomainsToClear=pAppDomain; + } + else + ppDomain=&((*ppDomain)->m_pNextInDelayedUnloadList); + } + } + + for (AppDomain* pDomain=pDomainsToClear;pDomain!=NULL;) + { + AppDomain* pNext=pDomain->m_pNextInDelayedUnloadList; + pDomain->Close(); //NOTHROW! + pDomain->Release(); + pDomain=pNext; + } +} + +void SystemDomain::ProcessClearingDomains() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + CrstHolder lh(&m_DelayedUnloadCrst); + + for (AppDomain** ppDomain=&m_pDelayedUnloadList;(*ppDomain)!=NULL; ) + { + if ((*ppDomain)->m_Stage==AppDomain::STAGE_HANDLETABLE_NOACCESS) + { + AppDomain* pAppDomain=*ppDomain; + pAppDomain->SetStage(AppDomain::STAGE_CLEARED); + } + ppDomain=&((*ppDomain)->m_pNextInDelayedUnloadList); + } + + if (!m_UnloadIsAsync) + { + // For synchronous mode, we are now done with the list. + m_pDelayedUnloadList = NULL; + } +} + +void SystemDomain::ProcessDelayedUnloadDomains() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + int iGCRefPoint=GCHeap::GetGCHeap()->CollectionCount(GCHeap::GetGCHeap()->GetMaxGeneration()); + if (GCHeap::GetGCHeap()->IsConcurrentGCInProgress()) + iGCRefPoint--; + + BOOL bAppDomainToCleanup = FALSE; + LoaderAllocator * pAllocatorsToDelete = NULL; + + { + CrstHolder lh(&m_DelayedUnloadCrst); + + for (AppDomain* pDomain=m_pDelayedUnloadList; pDomain!=NULL; pDomain=pDomain->m_pNextInDelayedUnloadList) + { + if (pDomain->m_Stage==AppDomain::STAGE_CLEARED) + { + // Compare with 0 to handle overflows gracefully + if (0 < iGCRefPoint - pDomain->GetGCRefPoint()) + { + bAppDomainToCleanup=TRUE; + pDomain->SetStage(AppDomain::STAGE_COLLECTED); + } + } + } + + LoaderAllocator ** ppAllocator=&m_pDelayedUnloadListOfLoaderAllocators; + while (*ppAllocator!= NULL) + { + LoaderAllocator * pAllocator = *ppAllocator; + if (0 < iGCRefPoint - pAllocator->GetGCRefPoint()) + { + *ppAllocator = pAllocator->m_pLoaderAllocatorDestroyNext; + + pAllocator->m_pLoaderAllocatorDestroyNext = pAllocatorsToDelete; + pAllocatorsToDelete = pAllocator; + } + else + { + ppAllocator = &pAllocator->m_pLoaderAllocatorDestroyNext; + } + } + } + + if (bAppDomainToCleanup) + AppDomain::EnableADUnloadWorkerForCollectedADCleanup(); + + // Delete collected loader allocators on the finalizer thread. We cannot offload it to appdomain unload thread because of + // there is not guaranteed to be one, and it is not that expensive operation anyway. + while (pAllocatorsToDelete != NULL) + { + LoaderAllocator * pAllocator = pAllocatorsToDelete; + pAllocatorsToDelete = pAllocator->m_pLoaderAllocatorDestroyNext; + delete pAllocator; + } +} + +#endif // CROSSGEN_COMPILE + +AppDomainFromIDHolder::AppDomainFromIDHolder(ADID adId, BOOL bUnsafePoint, SyncType synctype) +{ + WRAPPER_NO_CONTRACT; + ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT; +#ifdef _DEBUG + m_bAcquired=false; + m_bChecked=false; + m_type=synctype; + +#endif + Assign(adId, bUnsafePoint); +} + +AppDomainFromIDHolder::AppDomainFromIDHolder(SyncType synctype) +{ + LIMITED_METHOD_CONTRACT; + ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT; + m_pDomain=NULL; +#ifdef _DEBUG + m_bAcquired=false; + m_bChecked=false; + m_type=synctype; +#endif +} + +#ifndef CROSSGEN_COMPILE +void ADUnloadSink::ReportUnloadResult (HRESULT hr, OBJECTREF* pException) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(CheckPointer(this)); + PRECONDITION(m_UnloadCompleteEvent.IsValid()); + } + CONTRACTL_END; + + //pException is unused; + m_UnloadResult=hr; + m_UnloadCompleteEvent.Set(); +}; + +void ADUnloadSink::WaitUnloadCompletion() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + PRECONDITION(CheckPointer(this)); + PRECONDITION(m_UnloadCompleteEvent.IsValid()); + } + CONTRACTL_END; + + CONTRACT_VIOLATION(FaultViolation); + m_UnloadCompleteEvent.WaitEx(INFINITE, (WaitMode)(WaitMode_Alertable | WaitMode_ADUnload)); +}; + +ADUnloadSink* AppDomain::PrepareForWaitUnloadCompletion() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(SystemDomain::IsUnderDomainLock()); + FORBID_FAULT; + } + CONTRACTL_END; + + ADUnloadSink* pADSink=GetADUnloadSink(); + PREFIX_ASSUME(pADSink!=NULL); + if (m_Stage < AppDomain::STAGE_UNLOAD_REQUESTED) //we're first + { + pADSink->Reset(); + SetUnloadRequestThread(GetThread()); + } + return pADSink; +}; + +ADUnloadSink::ADUnloadSink() +{ + CONTRACTL + { + CONSTRUCTOR_CHECK; + THROWS; + GC_NOTRIGGER; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + m_cRef=1; + m_UnloadCompleteEvent.CreateManualEvent(FALSE); + m_UnloadResult=S_OK; +}; + +ADUnloadSink::~ADUnloadSink() +{ + CONTRACTL + { + DESTRUCTOR_CHECK; + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + m_UnloadCompleteEvent.CloseEvent(); + +}; + + +ULONG ADUnloadSink::AddRef() +{ + LIMITED_METHOD_CONTRACT; + return InterlockedIncrement(&m_cRef); +}; + +ULONG ADUnloadSink::Release() +{ + LIMITED_METHOD_CONTRACT; + ULONG ulRef = InterlockedDecrement(&m_cRef); + if (ulRef == 0) + { + delete this; + return 0; + } + return ulRef; +}; + +void ADUnloadSink::Reset() +{ + LIMITED_METHOD_CONTRACT; + m_UnloadResult=S_OK; + m_UnloadCompleteEvent.Reset(); +} + +ADUnloadSink* AppDomain::GetADUnloadSink() +{ + LIMITED_METHOD_CONTRACT; + _ASSERTE(SystemDomain::IsUnderDomainLock()); + if(m_ADUnloadSink) + m_ADUnloadSink->AddRef(); + return m_ADUnloadSink; +}; + +ADUnloadSink* AppDomain::GetADUnloadSinkForUnload() +{ + // unload thread only. Doesn't need to have AD lock + LIMITED_METHOD_CONTRACT; + if(m_ADUnloadSink) + m_ADUnloadSink->AddRef(); + return m_ADUnloadSink; +} +#endif // CROSSGEN_COMPILE + +void AppDomain::EnumStaticGCRefs(promote_func* fn, ScanContext* sc) +{ + CONTRACT_VOID + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_END; + + _ASSERTE(GCHeap::IsGCInProgress() && + GCHeap::IsServerHeap() && + IsGCSpecialThread()); + + AppDomain::AssemblyIterator asmIterator = IterateAssembliesEx((AssemblyIterationFlags)(kIncludeLoaded | kIncludeExecution)); + CollectibleAssemblyHolder pDomainAssembly; + while (asmIterator.Next(pDomainAssembly.This())) + { + // @TODO: Review when DomainAssemblies get added. + _ASSERTE(pDomainAssembly != NULL); + pDomainAssembly->EnumStaticGCRefs(fn, sc); + } + + RETURN; +} + +#endif // !DACCESS_COMPILE + +//------------------------------------------------------------------------ +UINT32 BaseDomain::GetTypeID(PTR_MethodTable pMT) { + CONTRACTL { + THROWS; + GC_TRIGGERS; + PRECONDITION(pMT->GetDomain() == this); + } CONTRACTL_END; + + return m_typeIDMap.GetTypeID(pMT); +} + +//------------------------------------------------------------------------ +// Returns the ID of the type if found. If not found, returns INVALID_TYPE_ID +UINT32 BaseDomain::LookupTypeID(PTR_MethodTable pMT) +{ + CONTRACTL { + NOTHROW; + SO_TOLERANT; + WRAPPER(GC_TRIGGERS); + PRECONDITION(pMT->GetDomain() == this); + } CONTRACTL_END; + + return m_typeIDMap.LookupTypeID(pMT); +} + +//------------------------------------------------------------------------ +PTR_MethodTable BaseDomain::LookupType(UINT32 id) { + CONTRACTL { + NOTHROW; + SO_TOLERANT; + WRAPPER(GC_TRIGGERS); + CONSISTENCY_CHECK(id != TYPE_ID_THIS_CLASS); + } CONTRACTL_END; + + PTR_MethodTable pMT = m_typeIDMap.LookupType(id); + if (pMT == NULL && !IsSharedDomain()) { + pMT = SharedDomain::GetDomain()->LookupType(id); + } + + CONSISTENCY_CHECK(CheckPointer(pMT)); + CONSISTENCY_CHECK(pMT->IsInterface()); + return pMT; +} + +#ifndef DACCESS_COMPILE + +#ifndef FEATURE_CORECLR +//------------------------------------------------------------------------ +DWORD* SetupCompatibilityFlags() +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + } CONTRACTL_END; + + LPCWSTR buf; + bool return_null = true; + + FAULT_NOT_FATAL(); // we can simply give up + + BEGIN_SO_INTOLERANT_CODE_NO_THROW_CHECK_THREAD(SetLastError(COR_E_STACKOVERFLOW); return NULL;) + InlineSString<4> bufString; + + if (WszGetEnvironmentVariable(W("UnsupportedCompatSwitchesEnabled"), bufString) != 0) + { + buf = bufString.GetUnicode(); + if (buf[0] != '1' || buf[1] != '\0') + { + return_null = true; + } + else + { + return_null = false; + } + + } + END_SO_INTOLERANT_CODE + + if (return_null) + return NULL; + + static const LPCWSTR rgFlagNames[] = { +#define COMPATFLAGDEF(name) TEXT(#name), +#include "compatibilityflagsdef.h" + }; + + int size = (compatCount+31) / 32; + DWORD* pFlags = new (nothrow) DWORD[size]; + if (pFlags == NULL) + return NULL; + ZeroMemory(pFlags, size * sizeof(DWORD)); + + BEGIN_SO_INTOLERANT_CODE_NO_THROW_CHECK_THREAD(SetLastError(COR_E_STACKOVERFLOW); return NULL;) + InlineSString<4> bufEnvString; + for (int i = 0; i < COUNTOF(rgFlagNames); i++) + { + if (WszGetEnvironmentVariable(rgFlagNames[i], bufEnvString) == 0) + continue; + + buf = bufEnvString.GetUnicode(); + if (buf[0] != '1' || buf[1] != '\0') + continue; + + pFlags[i / 32] |= 1 << (i % 32); + } + END_SO_INTOLERANT_CODE + + return pFlags; +} + +//------------------------------------------------------------------------ +static VolatilePtr g_pCompatibilityFlags = (DWORD*)(-1); + +DWORD* GetGlobalCompatibilityFlags() +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + } CONTRACTL_END; + + if (g_pCompatibilityFlags == (DWORD*)(-1)) + { + DWORD *pCompatibilityFlags = SetupCompatibilityFlags(); + + if (FastInterlockCompareExchangePointer(g_pCompatibilityFlags.GetPointer(), pCompatibilityFlags, reinterpret_cast(-1)) != (VOID*)(-1)) + { + delete [] pCompatibilityFlags; + } + } + + return g_pCompatibilityFlags; +} +#endif // !FEATURE_CORECLR + +//------------------------------------------------------------------------ +BOOL GetCompatibilityFlag(CompatibilityFlag flag) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + } CONTRACTL_END; + +#ifndef FEATURE_CORECLR + DWORD *pFlags = GetGlobalCompatibilityFlags(); + + if (pFlags != NULL) + return (pFlags[flag / 32] & (1 << (flag % 32))) ? TRUE : FALSE; + else + return FALSE; +#else // !FEATURE_CORECLR + return FALSE; +#endif // !FEATURE_CORECLR +} +#endif // !DACCESS_COMPILE + +//--------------------------------------------------------------------------------------- +// +BOOL +AppDomain::AssemblyIterator::Next( + CollectibleAssemblyHolder * pDomainAssemblyHolder) +{ + CONTRACTL { + NOTHROW; + WRAPPER(GC_TRIGGERS); // Triggers only in MODE_COOPERATIVE (by taking the lock) + MODE_ANY; + } CONTRACTL_END; + + CrstHolder ch(m_pAppDomain->GetAssemblyListLock()); + return Next_Unlocked(pDomainAssemblyHolder); +} + +//--------------------------------------------------------------------------------------- +// +// Note: Does not lock the assembly list, but locks collectible assemblies for adding references. +// +BOOL +AppDomain::AssemblyIterator::Next_Unlocked( + CollectibleAssemblyHolder * pDomainAssemblyHolder) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } CONTRACTL_END; + +#ifndef DACCESS_COMPILE + _ASSERTE(m_pAppDomain->GetAssemblyListLock()->OwnedByCurrentThread()); +#endif + + while (m_Iterator.Next()) + { + // Get element from the list/iterator (without adding reference to the assembly) + DomainAssembly * pDomainAssembly = dac_cast(m_Iterator.GetElement()); + if (pDomainAssembly == NULL) + { + continue; + } + + if (pDomainAssembly->IsError()) + { + if (m_assemblyIterationFlags & kIncludeFailedToLoad) + { + *pDomainAssemblyHolder = pDomainAssembly; + return TRUE; + } + continue; // reject + } + + // First, reject DomainAssemblies whose load status is not to be included in + // the enumeration + + if (pDomainAssembly->IsAvailableToProfilers() && + (m_assemblyIterationFlags & kIncludeAvailableToProfilers)) + { + // The assembly has reached the state at which we would notify profilers, + // and we're supposed to include such assemblies in the enumeration. So + // don't reject it (i.e., noop here, and don't bother with the rest of + // the load status checks). Check for this first, since + // kIncludeAvailableToProfilers contains some loaded AND loading + // assemblies. + } + else if (pDomainAssembly->IsLoaded()) + { + // A loaded assembly + if (!(m_assemblyIterationFlags & kIncludeLoaded)) + { + continue; // reject + } + } + else + { + // A loading assembly + if (!(m_assemblyIterationFlags & kIncludeLoading)) + { + continue; // reject + } + } + + // Next, reject DomainAssemblies whose execution / introspection status is + // not to be included in the enumeration + + if (pDomainAssembly->IsIntrospectionOnly()) + { + // introspection assembly + if (!(m_assemblyIterationFlags & kIncludeIntrospection)) + { + continue; // reject + } + } + else + { + // execution assembly + if (!(m_assemblyIterationFlags & kIncludeExecution)) + { + continue; // reject + } + } + + // Next, reject collectible assemblies + if (pDomainAssembly->IsCollectible()) + { + if (m_assemblyIterationFlags & kExcludeCollectible) + { + _ASSERTE(!(m_assemblyIterationFlags & kIncludeCollected)); + continue; // reject + } + + // Un-tenured collectible assemblies should not be returned. (This can only happen in a brief + // window during collectible assembly creation. No thread should need to have a pointer + // to the just allocated DomainAssembly at this stage.) + if (!pDomainAssembly->GetAssembly()->GetManifestModule()->IsTenured()) + { + continue; // reject + } + + if (pDomainAssembly->GetLoaderAllocator()->AddReferenceIfAlive()) + { // The assembly is alive + + // Set the holder value (incl. increasing ref-count) + *pDomainAssemblyHolder = pDomainAssembly; + + // Now release the reference we took in the if-condition + pDomainAssembly->GetLoaderAllocator()->Release(); + return TRUE; + } + // The assembly is not alive anymore (and we didn't increase its ref-count in the + // if-condition) + + if (!(m_assemblyIterationFlags & kIncludeCollected)) + { + continue; // reject + } + // Set the holder value to assembly with 0 ref-count without increasing the ref-count (won't + // call Release either) + pDomainAssemblyHolder->Assign(pDomainAssembly, FALSE); + return TRUE; + } + + *pDomainAssemblyHolder = pDomainAssembly; + return TRUE; + } + + *pDomainAssemblyHolder = NULL; + return FALSE; +} // AppDomain::AssemblyIterator::Next_Unlocked + +#ifndef DACCESS_COMPILE + +//--------------------------------------------------------------------------------------- +// +// Can be called only from AppDomain shutdown code:AppDomain::ShutdownAssemblies. +// Does not add-ref collectible assemblies (as the LoaderAllocator might not be reachable from the +// DomainAssembly anymore). +// +BOOL +AppDomain::AssemblyIterator::Next_UnsafeNoAddRef( + DomainAssembly ** ppDomainAssembly) +{ + CONTRACTL { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } CONTRACTL_END; + + // Make sure we are iterating all assemblies (see the only caller code:AppDomain::ShutdownAssemblies) + _ASSERTE(m_assemblyIterationFlags == + (kIncludeLoaded | kIncludeLoading | kIncludeExecution | kIncludeIntrospection | kIncludeFailedToLoad | kIncludeCollected)); + // It also means that we do not exclude anything + _ASSERTE((m_assemblyIterationFlags & kExcludeCollectible) == 0); + + // We are on shutdown path, so lock shouldn't be neccessary, but all _Unlocked methods on AssemblyList + // have asserts that the lock is held, so why not to take it ... + CrstHolder ch(m_pAppDomain->GetAssemblyListLock()); + + while (m_Iterator.Next()) + { + // Get element from the list/iterator (without adding reference to the assembly) + *ppDomainAssembly = dac_cast(m_Iterator.GetElement()); + if (*ppDomainAssembly == NULL) + { + continue; + } + + return TRUE; + } + + *ppDomainAssembly = NULL; + return FALSE; +} // AppDomain::AssemblyIterator::Next_UnsafeNoAddRef + +#ifdef FEATURE_CORECLR + +//--------------------------------------------------------------------------------------- +// +BOOL AppDomain::IsImageFromTrustedPath(PEImage* pPEImage) +{ + CONTRACTL + { + MODE_ANY; + GC_TRIGGERS; + THROWS; + PRECONDITION(CheckPointer(pPEImage)); + } + CONTRACTL_END; + + BOOL fIsInGAC = FALSE; + const SString &sImagePath = pPEImage->GetPath(); + + if (!sImagePath.IsEmpty()) + { + // If we're not in a sandboxed domain, everything is full trust all the time + if (GetSecurityDescriptor()->IsFullyTrusted()) + { + return TRUE; + } + + fIsInGAC = GetTPABinderContext()->IsInTpaList(sImagePath); + } + + return fIsInGAC; +} + +BOOL AppDomain::IsImageFullyTrusted(PEImage* pPEImage) +{ + WRAPPER_NO_CONTRACT; + return IsImageFromTrustedPath(pPEImage); +} + +#endif //FEATURE_CORECLR + +#endif //!DACCESS_COMPILE + +#if defined(FEATURE_HOST_ASSEMBLY_RESOLVER) && !defined(DACCESS_COMPILE) && !defined(CROSSGEN_COMPILE) + +// Returns a BOOL indicating if the binding model has been locked for the AppDomain +BOOL AppDomain::IsBindingModelLocked() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + return m_fIsBindingModelLocked.Load(); +} + +// Marks the binding model locked for AppDomain +BOOL AppDomain::LockBindingModel() +{ + LIMITED_METHOD_CONTRACT; + + BOOL fDidWeLockBindingModel = FALSE; + + if (InterlockedCompareExchangeT(&m_fIsBindingModelLocked, TRUE, FALSE) == FALSE) + { + fDidWeLockBindingModel = TRUE; + } + + return fDidWeLockBindingModel; +} + +BOOL AppDomain::IsHostAssemblyResolverInUse() +{ + LIMITED_METHOD_CONTRACT; + + return (GetFusionContext() != GetTPABinderContext()); +} + +// Helper used by the assembly binder to check if the specified AppDomain can use apppath assembly resolver +BOOL RuntimeCanUseAppPathAssemblyResolver(DWORD adid) +{ + CONTRACTL + { + NOTHROW; // Cannot throw since it is invoked by the Binder that expects to get a hresult + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + ADID id(adid); + + // We need to be in COOP mode to get the AppDomain* + GCX_COOP(); + + AppDomain *pTargetDomain = SystemDomain::GetAppDomainFromId(id, ADV_CURRENTAD); + _ASSERTE(pTargetDomain != NULL); + + pTargetDomain->LockBindingModel(); + + return !pTargetDomain->IsHostAssemblyResolverInUse(); +} + +// Returns S_OK if the assembly was successfully loaded +HRESULT RuntimeInvokeHostAssemblyResolver(INT_PTR pManagedAssemblyLoadContextToBindWithin, IAssemblyName *pIAssemblyName, CLRPrivBinderCoreCLR *pTPABinder, BINDER_SPACE::AssemblyName *pAssemblyName, ICLRPrivAssembly **ppLoadedAssembly) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(ppLoadedAssembly != NULL); + } + CONTRACTL_END; + + HRESULT hr = E_FAIL; + + // DevDiv #933506: Exceptions thrown during AssemblyLoadContext.Load should propagate + // EX_TRY + { + // Switch to COOP mode since we are going to work with managed references + GCX_COOP(); + + struct + { + ASSEMBLYNAMEREF oRefAssemblyName; + ASSEMBLYREF oRefLoadedAssembly; + } _gcRefs; + + ZeroMemory(&_gcRefs, sizeof(_gcRefs)); + + GCPROTECT_BEGIN(_gcRefs); + + ICLRPrivAssembly *pAssemblyBindingContext = NULL; + + bool fInvokedForTPABinder = (pTPABinder == NULL)?true:false; + + // Prepare to invoke System.Runtime.Loader.AssemblyLoadContext.Resolve method. + // + // First, initialize an assembly spec for the requested assembly + // + AssemblySpec spec; + hr = spec.Init(pIAssemblyName); + if (SUCCEEDED(hr)) + { + bool fResolvedAssembly = false; + bool fResolvedAssemblyViaTPALoadContext = false; + + // Allocate an AssemblyName managed object + _gcRefs.oRefAssemblyName = (ASSEMBLYNAMEREF) AllocateObject(MscorlibBinder::GetClass(CLASS__ASSEMBLY_NAME)); + + // Initialize the AssemblyName object from the AssemblySpec + spec.AssemblyNameInit(&_gcRefs.oRefAssemblyName, NULL); + + if (!fInvokedForTPABinder) + { + // Step 2 (of CLRPrivBinderAssemblyLoadContext::BindUsingAssemblyName) - Invoke Load method + // This is not invoked for TPA Binder since it always returns NULL. + + // Finally, setup arguments for invocation + BinderMethodID idHAR_Resolve = METHOD__ASSEMBLYLOADCONTEXT__RESOLVE; + MethodDescCallSite methLoadAssembly(idHAR_Resolve); + + // Setup the arguments for the call + ARG_SLOT args[2] = + { + PtrToArgSlot(pManagedAssemblyLoadContextToBindWithin), // IntPtr for managed assembly load context instance + ObjToArgSlot(_gcRefs.oRefAssemblyName), // AssemblyName instance + }; + + // Make the call + _gcRefs.oRefLoadedAssembly = (ASSEMBLYREF) methLoadAssembly.Call_RetOBJECTREF(args); + if (_gcRefs.oRefLoadedAssembly != NULL) + { + fResolvedAssembly = true; + } + + // Step 3 (of CLRPrivBinderAssemblyLoadContext::BindUsingAssemblyName) + if (!fResolvedAssembly) + { + // If we could not resolve the assembly using Load method, then attempt fallback with TPA Binder. + // Since TPA binder cannot fallback to itself, this fallback does not happen for binds within TPA binder. + // + // Switch to pre-emp mode before calling into the binder + GCX_PREEMP(); + ICLRPrivAssembly *pCoreCLRFoundAssembly = NULL; + hr = pTPABinder->BindAssemblyByName(pIAssemblyName, &pCoreCLRFoundAssembly); + if (SUCCEEDED(hr)) + { + pAssemblyBindingContext = pCoreCLRFoundAssembly; + fResolvedAssembly = true; + fResolvedAssemblyViaTPALoadContext = true; + } + } + } + + if (!fResolvedAssembly) + { + // Step 4 (of CLRPrivBinderAssemblyLoadContext::BindUsingAssemblyName) + // + // If we couldnt resolve the assembly using TPA LoadContext as well, then + // attempt to resolve it using the Resolving event. + // Finally, setup arguments for invocation + BinderMethodID idHAR_ResolveUsingEvent = METHOD__ASSEMBLYLOADCONTEXT__RESOLVEUSINGEVENT; + MethodDescCallSite methLoadAssembly(idHAR_ResolveUsingEvent); + + // Setup the arguments for the call + ARG_SLOT args[2] = + { + PtrToArgSlot(pManagedAssemblyLoadContextToBindWithin), // IntPtr for managed assembly load context instance + ObjToArgSlot(_gcRefs.oRefAssemblyName), // AssemblyName instance + }; + + // Make the call + _gcRefs.oRefLoadedAssembly = (ASSEMBLYREF) methLoadAssembly.Call_RetOBJECTREF(args); + if (_gcRefs.oRefLoadedAssembly != NULL) + { + // Set the flag indicating we found the assembly + fResolvedAssembly = true; + } + } + + if (fResolvedAssembly && !fResolvedAssemblyViaTPALoadContext) + { + // If we are here, assembly was successfully resolved via Load or Resolving events. + _ASSERTE(_gcRefs.oRefLoadedAssembly != NULL); + + // We were able to get the assembly loaded. Now, get its name since the host could have + // performed the resolution using an assembly with different name. + DomainAssembly *pDomainAssembly = _gcRefs.oRefLoadedAssembly->GetDomainAssembly(); + PEAssembly *pLoadedPEAssembly = NULL; + bool fFailLoad = false; + if (!pDomainAssembly) + { + // Reflection emitted assemblies will not have a domain assembly. + fFailLoad = true; + } + else + { + pLoadedPEAssembly = pDomainAssembly->GetFile(); + if (pLoadedPEAssembly->HasHostAssembly() != true) + { + // Reflection emitted assemblies will not have a domain assembly. + fFailLoad = true; + } + } + + // The loaded assembly's ICLRPrivAssembly* is saved as HostAssembly in PEAssembly + if (fFailLoad) + { + SString name; + spec.GetFileOrDisplayName(0, name); + COMPlusThrowHR(COR_E_INVALIDOPERATION, IDS_HOST_ASSEMBLY_RESOLVER_DYNAMICALLY_EMITTED_ASSEMBLIES_UNSUPPORTED, name); + } + + // Is the assembly already bound using a binding context that will be incompatible? + // An example is attempting to consume an assembly bound to WinRT binder. + pAssemblyBindingContext = pLoadedPEAssembly->GetHostAssembly(); + } + +#ifdef FEATURE_COMINTEROP + if (AreSameBinderInstance(pAssemblyBindingContext, GetAppDomain()->GetWinRtBinder())) + { + // It is invalid to return an assembly bound to an incompatible binder + *ppLoadedAssembly = NULL; + SString name; + spec.GetFileOrDisplayName(0, name); + COMPlusThrowHR(COR_E_INVALIDOPERATION, IDS_HOST_ASSEMBLY_RESOLVER_INCOMPATIBLE_BINDING_CONTEXT, name); + } +#endif // FEATURE_COMINTEROP + + // Get the ICLRPrivAssembly reference to return back to. + *ppLoadedAssembly = clr::SafeAddRef(pAssemblyBindingContext); + hr = S_OK; + } + + GCPROTECT_END(); + } + // EX_CATCH_HRESULT(hr); + + return hr; + +} +#endif // defined(FEATURE_HOST_ASSEMBLY_RESOLVER) && !defined(DACCESS_COMPILE) && !defined(CROSSGEN_COMPILE) + +//approximate size of loader data +//maintained for each assembly +#define APPROX_LOADER_DATA_PER_ASSEMBLY 8196 + +size_t AppDomain::EstimateSize() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + size_t retval = sizeof(AppDomain); + retval += GetLoaderAllocator()->EstimateSize(); + //very rough estimate + retval += GetAssemblyCount() * APPROX_LOADER_DATA_PER_ASSEMBLY; + return retval; +} + +#ifdef DACCESS_COMPILE + +void +DomainLocalModule::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) +{ + SUPPORTS_DAC; + + // Enumerate the DomainLocalModule itself. DLMs are allocated to be larger than + // sizeof(DomainLocalModule) to make room for ClassInit flags and non-GC statics. + // "DAC_ENUM_DTHIS()" probably does not account for this, so we might not enumerate + // all of the ClassInit flags and non-GC statics. + // sizeof(DomainLocalModule) == 0x28 + DAC_ENUM_DTHIS(); + + if (m_pDomainFile.IsValid()) + { + m_pDomainFile->EnumMemoryRegions(flags); + } + + if (m_pDynamicClassTable.Load().IsValid()) + { + DacEnumMemoryRegion(dac_cast(m_pDynamicClassTable.Load()), + m_aDynamicEntries * sizeof(DynamicClassInfo)); + + for (SIZE_T i = 0; i < m_aDynamicEntries; i++) + { + PTR_DynamicEntry entry = dac_cast(m_pDynamicClassTable[i].m_pDynamicEntry.Load()); + if (entry.IsValid()) + { + // sizeof(DomainLocalModule::DynamicEntry) == 8 + entry.EnumMem(); + } + } + } +} + +void +DomainLocalBlock::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) +{ + SUPPORTS_DAC; + // Block is contained in AppDomain, don't enum this. + + if (m_pModuleSlots.IsValid()) + { + DacEnumMemoryRegion(dac_cast(m_pModuleSlots), + m_aModuleIndices * sizeof(TADDR)); + + for (SIZE_T i = 0; i < m_aModuleIndices; i++) + { + PTR_DomainLocalModule domMod = m_pModuleSlots[i]; + if (domMod.IsValid()) + { + domMod->EnumMemoryRegions(flags); + } + } + } +} + +void +BaseDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, + bool enumThis) +{ + SUPPORTS_DAC; + if (enumThis) + { + // This is wrong. Don't do it. + // BaseDomain cannot be instantiated. + // The only thing this code can hope to accomplish is to potentially break + // memory enumeration walking through the derived class if we + // explicitly call the base class enum first. +// DAC_ENUM_VTHIS(); + } + + EMEM_OUT(("MEM: %p BaseDomain\n", dac_cast(this))); +} + +void +AppDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, + bool enumThis) +{ + SUPPORTS_DAC; + + if (enumThis) + { + //sizeof(AppDomain) == 0xeb0 + DAC_ENUM_VTHIS(); + } + BaseDomain::EnumMemoryRegions(flags, false); + + // We don't need AppDomain name in triage dumps. + if (flags != CLRDATA_ENUM_MEM_TRIAGE) + { + m_friendlyName.EnumMemoryRegions(flags); + } + + m_Assemblies.EnumMemoryRegions(flags); + AssemblyIterator assem = IterateAssembliesEx((AssemblyIterationFlags)(kIncludeLoaded | kIncludeExecution | kIncludeIntrospection)); + CollectibleAssemblyHolder pDomainAssembly; + + while (assem.Next(pDomainAssembly.This())) + { + pDomainAssembly->EnumMemoryRegions(flags); + } + + m_sDomainLocalBlock.EnumMemoryRegions(flags); + + m_LoaderAllocator.EnumMemoryRegions(flags); +} + +void +SystemDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, + bool enumThis) +{ + SUPPORTS_DAC; + if (enumThis) + { + DAC_ENUM_VTHIS(); + } + BaseDomain::EnumMemoryRegions(flags, false); + + if (m_pSystemFile.IsValid()) + { + m_pSystemFile->EnumMemoryRegions(flags); + } + if (m_pSystemAssembly.IsValid()) + { + m_pSystemAssembly->EnumMemoryRegions(flags); + } + if (m_pDefaultDomain.IsValid()) + { + m_pDefaultDomain->EnumMemoryRegions(flags, true); + } + + m_appDomainIndexList.EnumMem(); + (&m_appDomainIndexList)->EnumMemoryRegions(flags); +} + +void +SharedDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, + bool enumThis) +{ + SUPPORTS_DAC; + if (enumThis) + { + DAC_ENUM_VTHIS(); + } + BaseDomain::EnumMemoryRegions(flags, false); +#ifdef FEATURE_LOADER_OPTIMIZATION + m_assemblyMap.EnumMemoryRegions(flags); + SharedAssemblyIterator assem; + while (assem.Next()) + { + assem.GetAssembly()->EnumMemoryRegions(flags); + } +#endif +} + +#endif //DACCESS_COMPILE + + +PTR_LoaderAllocator SystemDomain::GetGlobalLoaderAllocator() +{ + return PTR_LoaderAllocator(PTR_HOST_MEMBER_TADDR(SystemDomain,System(),m_GlobalAllocator)); +} + +#ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING + +#ifndef CROSSGEN_COMPILE +// Return the total processor time (user and kernel) used by threads executing in this AppDomain so far. The +// result is in 100ns units. +ULONGLONG AppDomain::QueryProcessorUsage() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + +#ifndef DACCESS_COMPILE + Thread *pThread = NULL; + + // Need to update our accumulated processor time count with current values from each thread that is + // currently executing in this domain. + + // Take the thread store lock while we enumerate threads. + ThreadStoreLockHolder tsl; + while ((pThread = ThreadStore::GetThreadList(pThread)) != NULL) + { + // Skip unstarted and dead threads and those that are currently executing in a different AppDomain. + if (pThread->IsUnstarted() || pThread->IsDead() || pThread->GetDomain(INDEBUG(TRUE)) != this) + continue; + + // Add the amount of time spent by the thread in the AppDomain since the last time we asked (calling + // Thread::QueryThreadProcessorUsage() will reset the thread's counter). + UpdateProcessorUsage(pThread->QueryThreadProcessorUsage()); + } +#endif // !DACCESS_COMPILE + + // Return the updated total. + return m_ullTotalProcessorUsage; +} + +// Add to the current count of processor time used by threads within this AppDomain. This API is called by +// threads transitioning between AppDomains. +void AppDomain::UpdateProcessorUsage(ULONGLONG ullAdditionalUsage) +{ + LIMITED_METHOD_CONTRACT; + + // Need to be careful to synchronize here, multiple threads could be racing to update this count. + ULONGLONG ullOldValue; + ULONGLONG ullNewValue; + do + { + ullOldValue = m_ullTotalProcessorUsage; + ullNewValue = ullOldValue + ullAdditionalUsage; + } while (InterlockedCompareExchange64((LONGLONG*)&m_ullTotalProcessorUsage, + (LONGLONG)ullNewValue, + (LONGLONG)ullOldValue) != (LONGLONG)ullOldValue); +} +#endif // CROSSGEN_COMPILE + +#endif // FEATURE_APPDOMAIN_RESOURCE_MONITORING + +#if defined(FEATURE_TYPEEQUIVALENCE) + +#ifndef DACCESS_COMPILE +TypeEquivalenceHashTable * AppDomain::GetTypeEquivalenceCache() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + INJECT_FAULT(COMPlusThrowOM()); + MODE_ANY; + } + CONTRACTL_END; + + // Take the critical section all of the time in debug builds to ensure that it is safe to take + // the critical section in the unusual times when it may actually be needed in retail builds +#ifdef _DEBUG + CrstHolder ch(&m_TypeEquivalenceCrst); +#endif + + if (m_pTypeEquivalenceTable.Load() == NULL) + { +#ifndef _DEBUG + CrstHolder ch(&m_TypeEquivalenceCrst); +#endif + if (m_pTypeEquivalenceTable.Load() == NULL) + { + m_pTypeEquivalenceTable = TypeEquivalenceHashTable::Create(this, 12, &m_TypeEquivalenceCrst); + } + } + return m_pTypeEquivalenceTable; +} +#endif //!DACCESS_COMPILE + +#endif //FEATURE_TYPEEQUIVALENCE + +#if !defined(DACCESS_COMPILE) + +//--------------------------------------------------------------------------------------------------------------------- +void AppDomain::PublishHostedAssembly( + DomainAssembly * pDomainAssembly) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END + + if (pDomainAssembly->GetFile()->HasHostAssembly()) + { + // We have to serialize all Add operations + CrstHolder lockAdd(&m_crstHostAssemblyMapAdd); + _ASSERTE(m_hostAssemblyMap.Lookup(pDomainAssembly->GetFile()->GetHostAssembly()) == nullptr); + + // Wrapper for m_hostAssemblyMap.Add that avoids call out into host + HostAssemblyMap::AddPhases addCall; + + // 1. Preallocate one element + addCall.PreallocateForAdd(&m_hostAssemblyMap); + { + // 2. Take the reader lock which can be taken during stack walking + // We cannot call out into host from ForbidSuspend region (i.e. no allocations/deallocations) + ForbidSuspendThreadHolder suspend; + { + CrstHolder lock(&m_crstHostAssemblyMap); + // 3. Add the element to the hash table (no call out into host) + addCall.Add(pDomainAssembly); + } + } + // 4. Cleanup the old memory (if any) + addCall.DeleteOldTable(); + } + else + { +#ifdef FEATURE_APPX_BINDER + // In AppX processes, all PEAssemblies that are reach this stage should have host binders. + _ASSERTE(!AppX::IsAppXProcess()); +#endif + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void AppDomain::UpdatePublishHostedAssembly( + DomainAssembly * pAssembly, + PTR_PEFile pFile) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + CAN_TAKE_LOCK; + } + CONTRACTL_END + + if (pAssembly->GetFile()->HasHostAssembly()) + { + // We have to serialize all Add operations + CrstHolder lockAdd(&m_crstHostAssemblyMapAdd); + { + // Wrapper for m_hostAssemblyMap.Add that avoids call out into host + OriginalFileHostAssemblyMap::AddPhases addCall; + bool fAddOrigFile = false; + + // For cases where the pefile is being updated + // 1. Preallocate one element + if (pFile != pAssembly->GetFile()) + { + addCall.PreallocateForAdd(&m_hostAssemblyMapForOrigFile); + fAddOrigFile = true; + } + + { + // We cannot call out into host from ForbidSuspend region (i.e. no allocations/deallocations) + ForbidSuspendThreadHolder suspend; + { + CrstHolder lock(&m_crstHostAssemblyMap); + + // Remove from hash table. + _ASSERTE(m_hostAssemblyMap.Lookup(pAssembly->GetFile()->GetHostAssembly()) != nullptr); + m_hostAssemblyMap.Remove(pAssembly->GetFile()->GetHostAssembly()); + + // Update PEFile on DomainAssembly. (This may cause the key for the hash to change, which is why we need this function) + pAssembly->UpdatePEFileWorker(pFile); + + _ASSERTE(fAddOrigFile == (pAssembly->GetOriginalFile() != pAssembly->GetFile())); + if (fAddOrigFile) + { + // Add to the orig file hash table if we might be in a case where we've cached the original pefile and not the final pe file (for use during GetAssemblyIfLoaded) + addCall.Add(pAssembly); + } + + // Add back to the hashtable (the call to Remove above guarantees that we will not call into host for table reallocation) + _ASSERTE(m_hostAssemblyMap.Lookup(pAssembly->GetFile()->GetHostAssembly()) == nullptr); + m_hostAssemblyMap.Add(pAssembly); + } + } + + // 4. Cleanup the old memory (if any) + if (fAddOrigFile) + addCall.DeleteOldTable(); + } + } + else + { +#ifdef FEATURE_APPX_BINDER + // In AppX processes, all PEAssemblies that are reach this stage should have host binders. + _ASSERTE(!AppX::IsAppXProcess()); +#endif + + pAssembly->UpdatePEFileWorker(pFile); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void AppDomain::UnPublishHostedAssembly( + DomainAssembly * pAssembly) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + CAN_TAKE_LOCK; + } + CONTRACTL_END + + if (pAssembly->GetFile()->HasHostAssembly()) + { + ForbidSuspendThreadHolder suspend; + { + CrstHolder lock(&m_crstHostAssemblyMap); + _ASSERTE(m_hostAssemblyMap.Lookup(pAssembly->GetFile()->GetHostAssembly()) != nullptr); + m_hostAssemblyMap.Remove(pAssembly->GetFile()->GetHostAssembly()); + + // We also have an entry in m_hostAssemblyMapForOrigFile. Handle that case. + if (pAssembly->GetOriginalFile() != pAssembly->GetFile()) + { + m_hostAssemblyMapForOrigFile.Remove(pAssembly->GetOriginalFile()->GetHostAssembly()); + } + } + } + else + { + // In AppX processes, all PEAssemblies that are reach this stage should have host binders. + _ASSERTE(!AppX::IsAppXProcess()); + } +} + +#if defined(FEATURE_CORECLR) && defined(FEATURE_COMINTEROP) +HRESULT AppDomain::SetWinrtApplicationContext(SString &appLocalWinMD) +{ + STANDARD_VM_CONTRACT; + + _ASSERTE(WinRTSupported()); + _ASSERTE(m_pWinRtBinder != nullptr); + + _ASSERTE(GetTPABinderContext() != NULL); + BINDER_SPACE::ApplicationContext *pApplicationContext = GetTPABinderContext()->GetAppContext(); + _ASSERTE(pApplicationContext != NULL); + + return m_pWinRtBinder->SetApplicationContext(pApplicationContext, appLocalWinMD); +} + +#endif // FEATURE_CORECLR && FEATURE_COMINTEROP + +#endif //!DACCESS_COMPILE + +//--------------------------------------------------------------------------------------------------------------------- +PTR_DomainAssembly AppDomain::FindAssembly(PTR_ICLRPrivAssembly pHostAssembly) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + SUPPORTS_DAC; + } + CONTRACTL_END + + if (pHostAssembly == nullptr) + return NULL; + + { + ForbidSuspendThreadHolder suspend; + { + CrstHolder lock(&m_crstHostAssemblyMap); + PTR_DomainAssembly returnValue = m_hostAssemblyMap.Lookup(pHostAssembly); + if (returnValue == NULL) + { + // If not found in the m_hostAssemblyMap, look in the m_hostAssemblyMapForOrigFile + // This is necessary as it may happen during in a second AppDomain that the PEFile + // first discovered in the AppDomain may not be used by the DomainFile, but the CLRPrivBinderFusion + // will in some cases find the pHostAssembly associated with this no longer used PEFile + // instead of the PEFile that was finally decided upon. + returnValue = m_hostAssemblyMapForOrigFile.Lookup(pHostAssembly); + } + + return returnValue; + } + } +} + +#if !defined(DACCESS_COMPILE) && defined(FEATURE_CORECLR) && defined(FEATURE_NATIVE_IMAGE_GENERATION) + +void ZapperSetBindingPaths(ICorCompilationDomain *pDomain, SString &trustedPlatformAssemblies, SString &platformResourceRoots, SString &appPaths, SString &appNiPaths) +{ + CLRPrivBinderCoreCLR *pBinder = static_cast(((CompilationDomain *)pDomain)->GetFusionContext()); + _ASSERTE(pBinder != NULL); + pBinder->SetupBindingPaths(trustedPlatformAssemblies, platformResourceRoots, appPaths, appNiPaths); +#ifdef FEATURE_COMINTEROP + SString emptString; + ((CompilationDomain*)pDomain)->SetWinrtApplicationContext(emptString); +#endif +} + +#endif + +#if defined(FEATURE_CORECLR) && !defined(CROSSGEN_COMPILE) +bool IsSingleAppDomain() +{ + STARTUP_FLAGS flags = CorHost2::GetStartupFlags(); + if(flags & STARTUP_SINGLE_APPDOMAIN) + return TRUE; + else + return FALSE; +} +#else +bool IsSingleAppDomain() +{ + return FALSE; +} +#endif diff --git a/src/vm/appdomain.hpp b/src/vm/appdomain.hpp new file mode 100644 index 0000000000..97e8438329 --- /dev/null +++ b/src/vm/appdomain.hpp @@ -0,0 +1,5291 @@ +// 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. + +/*============================================================ +** +** Header: AppDomain.cpp +** + +** +** Purpose: Implements AppDomain (loader domain) architecture +** +** +===========================================================*/ +#ifndef _APPDOMAIN_H +#define _APPDOMAIN_H + +#include "eventtrace.h" +#include "assembly.hpp" +#include "clsload.hpp" +#include "eehash.h" +#ifdef FEATURE_FUSION +#include "fusion.h" +#endif +#include "arraylist.h" +#include "comreflectioncache.hpp" +#include "comutilnative.h" +#include "domainfile.h" +#include "objectlist.h" +#include "fptrstubs.h" +#include "ilstubcache.h" +#include "testhookmgr.h" +#ifdef FEATURE_VERSIONING +#include "../binder/inc/applicationcontext.hpp" +#endif // FEATURE_VERSIONING +#include "rejit.h" + +#ifdef FEATURE_MULTICOREJIT +#include "multicorejit.h" +#endif + +#ifdef FEATURE_COMINTEROP +#include "clrprivbinderwinrt.h" +#ifndef FEATURE_CORECLR +#include "clrprivbinderreflectiononlywinrt.h" +#include "clrprivtypecachereflectiononlywinrt.h" +#endif +#include "..\md\winmd\inc\adapter.h" +#include "winrttypenameconverter.h" +#endif // FEATURE_COMINTEROP + +#include "appxutil.h" + +class BaseDomain; +class SystemDomain; +class SharedDomain; +class AppDomain; +class CompilationDomain; +class AppDomainEnum; +class AssemblySink; +class EEMarshalingData; +class Context; +class GlobalStringLiteralMap; +class StringLiteralMap; +struct SecurityContext; +class MngStdInterfacesInfo; +class DomainModule; +class DomainAssembly; +struct InteropMethodTableData; +class LoadLevelLimiter; +class UMEntryThunkCache; +class TypeEquivalenceHashTable; +class IApplicationSecurityDescriptor; +class StringArrayList; + +typedef VPTR(IApplicationSecurityDescriptor) PTR_IApplicationSecurityDescriptor; + +extern INT64 g_PauseTime; // Total time in millisecond the CLR has been paused + +#ifdef FEATURE_COMINTEROP +class ComCallWrapperCache; +struct SimpleComCallWrapper; + +class RCWRefCache; + +// This enum is used to specify whether user want COM or remoting +enum COMorRemotingFlag { + COMorRemoting_NotInitialized = 0, + COMorRemoting_COM = 1, // COM will be used both cross-domain and cross-runtime + COMorRemoting_Remoting = 2, // Remoting will be used cross-domain; cross-runtime will use Remoting only if it looks like it's expected (default) + COMorRemoting_LegacyMode = 3 // Remoting will be used both cross-domain and cross-runtime +}; + +#endif // FEATURE_COMINTEROP + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4200) // Disable zero-sized array warning +#endif + + +GPTR_DECL(IdDispenser, g_pModuleIndexDispenser); + +// This enum is aligned to System.ExceptionCatcherType. +enum ExceptionCatcher { + ExceptionCatcher_ManagedCode = 0, + ExceptionCatcher_AppDomainTransition = 1, + ExceptionCatcher_COMInterop = 2, +}; + +// We would like *ALLOCATECLASS_FLAG to AV (in order to catch errors), so don't change it +struct ClassInitFlags { + enum + { + INITIALIZED_FLAG_BIT = 0, + INITIALIZED_FLAG = 1<(dynamicClassInfoParam);\ + DomainLocalModule::PTR_DynamicEntry pDynamicEntry = dac_cast((DomainLocalModule::DynamicEntry*)dynamicClassInfo->m_pDynamicEntry.Load()); \ + if ((dynamicClassInfo->m_dwFlags) & ClassInitFlags::COLLECTIBLE_FLAG) \ + {\ + PTRARRAYREF objArray;\ + objArray = (PTRARRAYREF)pLoaderAllocator->GetHandleValueFastCannotFailType2( \ + (dac_cast(pDynamicEntry))->m_hGCStatics);\ + *(pGCStatics) = dac_cast(PTR_READ(PTR_TO_TADDR(OBJECTREFToObject( objArray )) + offsetof(PtrArray, m_Array), objArray->GetNumComponents() * sizeof(void*))) ;\ + }\ + else\ + {\ + *(pGCStatics) = (dac_cast(pDynamicEntry))->GetGCStaticsBasePointer();\ + }\ + }\ + +#define GET_DYNAMICENTRY_NONGCSTATICS_BASEPOINTER(pLoaderAllocator, dynamicClassInfoParam, pNonGCStatics) \ + {\ + DomainLocalModule::PTR_DynamicClassInfo dynamicClassInfo = dac_cast(dynamicClassInfoParam);\ + DomainLocalModule::PTR_DynamicEntry pDynamicEntry = dac_cast((DomainLocalModule::DynamicEntry*)(dynamicClassInfo)->m_pDynamicEntry.Load()); \ + if (((dynamicClassInfo)->m_dwFlags) & ClassInitFlags::COLLECTIBLE_FLAG) \ + {\ + if ((dac_cast(pDynamicEntry))->m_hNonGCStatics != 0) \ + { \ + U1ARRAYREF objArray;\ + objArray = (U1ARRAYREF)pLoaderAllocator->GetHandleValueFastCannotFailType2( \ + (dac_cast(pDynamicEntry))->m_hNonGCStatics);\ + *(pNonGCStatics) = dac_cast(PTR_READ( \ + PTR_TO_TADDR(OBJECTREFToObject( objArray )) + sizeof(ArrayBase) - DomainLocalModule::DynamicEntry::GetOffsetOfDataBlob(), \ + objArray->GetNumComponents() * (DWORD)objArray->GetComponentSize() + DomainLocalModule::DynamicEntry::GetOffsetOfDataBlob())); \ + } else (*pNonGCStatics) = NULL; \ + }\ + else\ + {\ + *(pNonGCStatics) = dac_cast(pDynamicEntry)->GetNonGCStaticsBasePointer();\ + }\ + }\ + + struct DynamicEntry + { + static DWORD GetOffsetOfDataBlob(); + }; + typedef DPTR(DynamicEntry) PTR_DynamicEntry; + + struct CollectibleDynamicEntry : public DynamicEntry + { + LOADERHANDLE m_hGCStatics; + LOADERHANDLE m_hNonGCStatics; + }; + typedef DPTR(CollectibleDynamicEntry) PTR_CollectibleDynamicEntry; + + struct NormalDynamicEntry : public DynamicEntry + { + PTR_OBJECTREF m_pGCStatics; +#ifdef FEATURE_64BIT_ALIGNMENT + // Padding to make m_pDataBlob aligned at MAX_PRIMITIVE_FIELD_SIZE + // code:MethodTableBuilder::PlaceRegularStaticFields assumes that the start of the data blob is aligned + SIZE_T m_padding; +#endif + BYTE m_pDataBlob[0]; + + inline PTR_BYTE GetGCStaticsBasePointer() + { + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + return dac_cast(m_pGCStatics); + } + inline PTR_BYTE GetNonGCStaticsBasePointer() + { + LIMITED_METHOD_CONTRACT + SUPPORTS_DAC; + return dac_cast(this); + } + }; + typedef DPTR(NormalDynamicEntry) PTR_NormalDynamicEntry; + + struct DynamicClassInfo + { + VolatilePtr m_pDynamicEntry; + Volatile m_dwFlags; + }; + typedef DPTR(DynamicClassInfo) PTR_DynamicClassInfo; + + inline UMEntryThunk * GetADThunkTable() + { + LIMITED_METHOD_CONTRACT + return m_pADThunkTable; + } + + inline void SetADThunkTable(UMEntryThunk* pADThunkTable) + { + LIMITED_METHOD_CONTRACT + InterlockedCompareExchangeT(m_pADThunkTable.GetPointer(), pADThunkTable, NULL); + } + + // Note the difference between: + // + // GetPrecomputedNonGCStaticsBasePointer() and + // GetPrecomputedStaticsClassData() + // + // GetPrecomputedNonGCStaticsBasePointer returns the pointer that should be added to field offsets to retrieve statics + // GetPrecomputedStaticsClassData returns a pointer to the first byte of the precomputed statics block + inline TADDR GetPrecomputedNonGCStaticsBasePointer() + { + LIMITED_METHOD_CONTRACT + return dac_cast(this); + } + + inline PTR_BYTE GetPrecomputedStaticsClassData() + { + LIMITED_METHOD_CONTRACT + return dac_cast(this) + offsetof(DomainLocalModule, m_pDataBlob); + } + + static SIZE_T GetOffsetOfDataBlob() { return offsetof(DomainLocalModule, m_pDataBlob); } + static SIZE_T GetOffsetOfGCStaticPointer() { return offsetof(DomainLocalModule, m_pGCStatics); } + + inline DomainFile* GetDomainFile() + { + LIMITED_METHOD_CONTRACT + SUPPORTS_DAC; + return m_pDomainFile; + } + +#ifndef DACCESS_COMPILE + inline void SetDomainFile(DomainFile* pDomainFile) + { + LIMITED_METHOD_CONTRACT + m_pDomainFile = pDomainFile; + } +#endif + + inline PTR_OBJECTREF GetPrecomputedGCStaticsBasePointer() + { + LIMITED_METHOD_CONTRACT + return m_pGCStatics; + } + + inline PTR_OBJECTREF * GetPrecomputedGCStaticsBasePointerAddress() + { + LIMITED_METHOD_CONTRACT + return &m_pGCStatics; + } + + // Returns bytes so we can add offsets + inline PTR_BYTE GetGCStaticsBasePointer(MethodTable * pMT) + { + WRAPPER_NO_CONTRACT + SUPPORTS_DAC; + + if (pMT->IsDynamicStatics()) + { + _ASSERTE(GetDomainFile()->GetModule() == pMT->GetModuleForStatics()); + return GetDynamicEntryGCStaticsBasePointer(pMT->GetModuleDynamicEntryID(), pMT->GetLoaderAllocator()); + } + else + { + return dac_cast(m_pGCStatics); + } + } + + inline PTR_BYTE GetNonGCStaticsBasePointer(MethodTable * pMT) + { + WRAPPER_NO_CONTRACT + SUPPORTS_DAC; + + if (pMT->IsDynamicStatics()) + { + _ASSERTE(GetDomainFile()->GetModule() == pMT->GetModuleForStatics()); + return GetDynamicEntryNonGCStaticsBasePointer(pMT->GetModuleDynamicEntryID(), pMT->GetLoaderAllocator()); + } + else + { + return dac_cast(this); + } + } + + inline DynamicClassInfo* GetDynamicClassInfo(DWORD n) + { + LIMITED_METHOD_CONTRACT + SUPPORTS_DAC; + _ASSERTE(m_pDynamicClassTable.Load() && m_aDynamicEntries > n); + dac_cast(m_pDynamicClassTable[n].m_pDynamicEntry.Load()); + + return &m_pDynamicClassTable[n]; + } + + // These helpers can now return null, as the debugger may do queries on a type + // before the calls to PopulateClass happen + inline PTR_BYTE GetDynamicEntryGCStaticsBasePointer(DWORD n, PTR_LoaderAllocator pLoaderAllocator) + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + MODE_COOPERATIVE; + SUPPORTS_DAC; + } + CONTRACTL_END; + + + if (n >= m_aDynamicEntries) + { + return NULL; + } + + DynamicClassInfo* pClassInfo = GetDynamicClassInfo(n); + if (!pClassInfo->m_pDynamicEntry) + { + return NULL; + } + + PTR_BYTE retval = NULL; + + GET_DYNAMICENTRY_GCSTATICS_BASEPOINTER(pLoaderAllocator, pClassInfo, &retval); + + return retval; + } + + inline PTR_BYTE GetDynamicEntryNonGCStaticsBasePointer(DWORD n, PTR_LoaderAllocator pLoaderAllocator) + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + MODE_COOPERATIVE; + SUPPORTS_DAC; + } + CONTRACTL_END; + + + if (n >= m_aDynamicEntries) + { + return NULL; + } + + DynamicClassInfo* pClassInfo = GetDynamicClassInfo(n); + if (!pClassInfo->m_pDynamicEntry) + { + return NULL; + } + + PTR_BYTE retval = NULL; + + GET_DYNAMICENTRY_NONGCSTATICS_BASEPOINTER(pLoaderAllocator, pClassInfo, &retval); + + return retval; + } + + FORCEINLINE PTR_DynamicClassInfo GetDynamicClassInfoIfInitialized(DWORD n) + { + WRAPPER_NO_CONTRACT; + + // m_aDynamicEntries is set last, it needs to be checked first + if (n >= m_aDynamicEntries) + { + return NULL; + } + + _ASSERTE(m_pDynamicClassTable.Load() != NULL); + PTR_DynamicClassInfo pDynamicClassInfo = (PTR_DynamicClassInfo)(m_pDynamicClassTable.Load() + n); + + // INITIALIZED_FLAG is set last, it needs to be checked first + if ((pDynamicClassInfo->m_dwFlags & ClassInitFlags::INITIALIZED_FLAG) == 0) + { + return NULL; + } + + PREFIX_ASSUME(pDynamicClassInfo != NULL); + return pDynamicClassInfo; + } + + // iClassIndex is slightly expensive to compute, so if we already know + // it, we can use this helper + inline BOOL IsClassInitialized(MethodTable* pMT, DWORD iClassIndex = (DWORD)-1) + { + WRAPPER_NO_CONTRACT; + return (GetClassFlags(pMT, iClassIndex) & ClassInitFlags::INITIALIZED_FLAG) != 0; + } + + inline BOOL IsPrecomputedClassInitialized(DWORD classID) + { + return GetPrecomputedStaticsClassData()[classID] & ClassInitFlags::INITIALIZED_FLAG; + } + + inline BOOL IsClassAllocated(MethodTable* pMT, DWORD iClassIndex = (DWORD)-1) + { + WRAPPER_NO_CONTRACT; + return (GetClassFlags(pMT, iClassIndex) & ClassInitFlags::ALLOCATECLASS_FLAG) != 0; + } + + BOOL IsClassInitError(MethodTable* pMT, DWORD iClassIndex = (DWORD)-1) + { + WRAPPER_NO_CONTRACT; + return (GetClassFlags(pMT, iClassIndex) & ClassInitFlags::ERROR_FLAG) != 0; + } + + void SetClassInitialized(MethodTable* pMT); + void SetClassInitError(MethodTable* pMT); + + void EnsureDynamicClassIndex(DWORD dwID); + + void AllocateDynamicClass(MethodTable *pMT); + + void PopulateClass(MethodTable *pMT); + +#ifdef DACCESS_COMPILE + void EnumMemoryRegions(CLRDataEnumMemoryFlags flags); +#endif + + static DWORD OffsetOfDataBlob() + { + LIMITED_METHOD_CONTRACT; + return offsetof(DomainLocalModule, m_pDataBlob); + } + + FORCEINLINE MethodTable * GetMethodTableFromClassDomainID(DWORD dwClassDomainID) + { + DWORD rid = (DWORD)(dwClassDomainID) + 1; + TypeHandle th = GetDomainFile()->GetModule()->LookupTypeDef(TokenFromRid(rid, mdtTypeDef)); + _ASSERTE(!th.IsNull()); + MethodTable * pMT = th.AsMethodTable(); + PREFIX_ASSUME(pMT != NULL); + return pMT; + } + +private: + friend void EmitFastGetSharedStaticBase(CPUSTUBLINKER *psl, CodeLabel *init, bool bCCtorCheck); + + void SetClassFlags(MethodTable* pMT, DWORD dwFlags); + DWORD GetClassFlags(MethodTable* pMT, DWORD iClassIndex); + + PTR_DomainFile m_pDomainFile; + VolatilePtr m_pDynamicClassTable; // used for generics and reflection.emit in memory + Volatile m_aDynamicEntries; // number of entries in dynamic table + VolatilePtr m_pADThunkTable; + PTR_OBJECTREF m_pGCStatics; // Handle to GC statics of the module + + // In addition to storing the ModuleIndex in the Module class, we also + // keep a copy of the ModuleIndex in the DomainLocalModule class. This + // allows the thread static JIT helpers to quickly convert a pointer to + // a DomainLocalModule into a ModuleIndex. + ModuleIndex m_ModuleIndex; + + // Note that the static offset calculation in code:Module::BuildStaticsOffsets takes the offset m_pDataBlob + // into consideration for alignment so we do not need any padding to ensure that the start of the data blob is aligned + + BYTE m_pDataBlob[0]; // First byte of the statics blob + + // Layout of m_pDataBlob is: + // ClassInit bytes (hold flags for cctor run, cctor error, etc) + // Non GC Statics + +public: + + // The Module class need to be able to initialized ModuleIndex, + // so for now I will make it a friend.. + friend class Module; + + FORCEINLINE ModuleIndex GetModuleIndex() + { + LIMITED_METHOD_DAC_CONTRACT; + return m_ModuleIndex; + } + +}; // struct DomainLocalModule + + +typedef DPTR(class DomainLocalBlock) PTR_DomainLocalBlock; +class DomainLocalBlock +{ + friend class ClrDataAccess; + friend class CheckAsmOffsets; + +private: + PTR_AppDomain m_pDomain; + DPTR(PTR_DomainLocalModule) m_pModuleSlots; + SIZE_T m_aModuleIndices; // Module entries the shared block has allocated + +public: // used by code generators + static SIZE_T GetOffsetOfModuleSlotsPointer() { return offsetof(DomainLocalBlock, m_pModuleSlots);} + +public: + +#ifndef DACCESS_COMPILE + DomainLocalBlock() + : m_pDomain(NULL), m_pModuleSlots(NULL), m_aModuleIndices(0) {} + + void EnsureModuleIndex(ModuleIndex index); + + void Init(AppDomain *pDomain) { LIMITED_METHOD_CONTRACT; m_pDomain = pDomain; } +#endif + + void SetModuleSlot(ModuleIndex index, PTR_DomainLocalModule pLocalModule); + + FORCEINLINE PTR_DomainLocalModule GetModuleSlot(ModuleIndex index) + { + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + _ASSERTE(index.m_dwIndex < m_aModuleIndices); + return m_pModuleSlots[index.m_dwIndex]; + } + + inline PTR_DomainLocalModule GetModuleSlot(MethodTable* pMT) + { + WRAPPER_NO_CONTRACT; + return GetModuleSlot(pMT->GetModuleForStatics()->GetModuleIndex()); + } + + DomainFile* TryGetDomainFile(ModuleIndex index) + { + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + // the publishing of m_aModuleIndices and m_pModuleSlots is dependent + // on the order of accesses; we must ensure that we read from m_aModuleIndices + // before m_pModuleSlots. + if (index.m_dwIndex < m_aModuleIndices) + { + MemoryBarrier(); + if (m_pModuleSlots[index.m_dwIndex]) + { + return m_pModuleSlots[index.m_dwIndex]->GetDomainFile(); + } + } + + return NULL; + } + + DomainFile* GetDomainFile(SIZE_T ModuleID) + { + WRAPPER_NO_CONTRACT; + ModuleIndex index = Module::IDToIndex(ModuleID); + _ASSERTE(index.m_dwIndex < m_aModuleIndices); + return m_pModuleSlots[index.m_dwIndex]->GetDomainFile(); + } + +#ifndef DACCESS_COMPILE + void SetDomainFile(ModuleIndex index, DomainFile* pDomainFile) + { + WRAPPER_NO_CONTRACT; + _ASSERTE(index.m_dwIndex < m_aModuleIndices); + m_pModuleSlots[index.m_dwIndex]->SetDomainFile(pDomainFile); + } +#endif + +#ifdef DACCESS_COMPILE + void EnumMemoryRegions(CLRDataEnumMemoryFlags flags); +#endif + + +private: + + // + // Low level routines to get & set class entries + // + +}; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + +// The large heap handle bucket class is used to contain handles allocated +// from an array contained in the large heap. +class LargeHeapHandleBucket +{ +public: + // Constructor and desctructor. + LargeHeapHandleBucket(LargeHeapHandleBucket *pNext, DWORD Size, BaseDomain *pDomain, BOOL bCrossAD = FALSE); + ~LargeHeapHandleBucket(); + + // This returns the next bucket. + LargeHeapHandleBucket *GetNext() + { + LIMITED_METHOD_CONTRACT; + + return m_pNext; + } + + // This returns the number of remaining handle slots. + DWORD GetNumRemainingHandles() + { + LIMITED_METHOD_CONTRACT; + + return m_ArraySize - m_CurrentPos; + } + + void ConsumeRemaining() + { + LIMITED_METHOD_CONTRACT; + + m_CurrentPos = m_ArraySize; + } + + OBJECTREF *TryAllocateEmbeddedFreeHandle(); + + // Allocate handles from the bucket. + OBJECTREF* AllocateHandles(DWORD nRequested); + OBJECTREF* CurrentPos() + { + LIMITED_METHOD_CONTRACT; + return m_pArrayDataPtr + m_CurrentPos; + } + +private: + LargeHeapHandleBucket *m_pNext; + int m_ArraySize; + int m_CurrentPos; + int m_CurrentEmbeddedFreePos; + OBJECTHANDLE m_hndHandleArray; + OBJECTREF *m_pArrayDataPtr; +}; + + + +// The large heap handle table is used to allocate handles that are pointers +// to objects stored in an array in the large object heap. +class LargeHeapHandleTable +{ +public: + // Constructor and desctructor. + LargeHeapHandleTable(BaseDomain *pDomain, DWORD InitialBucketSize); + ~LargeHeapHandleTable(); + + // Allocate handles from the large heap handle table. + OBJECTREF* AllocateHandles(DWORD nRequested, BOOL bCrossAD = FALSE); + + // Release object handles allocated using AllocateHandles(). + void ReleaseHandles(OBJECTREF *pObjRef, DWORD nReleased); + +private: + // The buckets of object handles. + LargeHeapHandleBucket *m_pHead; + + // We need to know the containing domain so we know where to allocate handles + BaseDomain *m_pDomain; + + // The size of the LargeHeapHandleBuckets. + DWORD m_NextBucketSize; + + // for finding and re-using embedded free items in the list + LargeHeapHandleBucket *m_pFreeSearchHint; + DWORD m_cEmbeddedFree; + +#ifdef _DEBUG + + // these functions are present to enforce that there is a locking mechanism in place + // for each LargeHeapHandleTable even though the code itself does not do the locking + // you must tell the table which lock you intend to use and it will verify that it has + // in fact been taken before performing any operations + +public: + void RegisterCrstDebug(CrstBase *pCrst) + { + LIMITED_METHOD_CONTRACT; + + // this function must be called exactly once + _ASSERTE(pCrst != NULL); + _ASSERTE(m_pCrstDebug == NULL); + m_pCrstDebug = pCrst; + } + +private: + // we will assert that this Crst is held before using the object + CrstBase *m_pCrstDebug; + +#endif + +}; + +class LargeHeapHandleBlockHolder; +void LargeHeapHandleBlockHolder__StaticFree(LargeHeapHandleBlockHolder*); + + +class LargeHeapHandleBlockHolder:public Holder + +{ + LargeHeapHandleTable* m_pTable; + DWORD m_Count; + OBJECTREF* m_Data; +public: + FORCEINLINE LargeHeapHandleBlockHolder(LargeHeapHandleTable* pOwner, DWORD nCount) + { + WRAPPER_NO_CONTRACT; + m_Data = pOwner->AllocateHandles(nCount); + m_Count=nCount; + m_pTable=pOwner; + }; + + FORCEINLINE void FreeData() + { + WRAPPER_NO_CONTRACT; + for (DWORD i=0;i< m_Count;i++) + ClearObjectReference(m_Data+i); + m_pTable->ReleaseHandles(m_Data, m_Count); + }; + FORCEINLINE OBJECTREF* operator[] (DWORD idx) + { + LIMITED_METHOD_CONTRACT; + _ASSERTE(idxFreeData(); +}; + + + + + +// The large heap handle bucket class is used to contain handles allocated +// from an array contained in the large heap. +class ThreadStaticHandleBucket +{ +public: + // Constructor and desctructor. + ThreadStaticHandleBucket(ThreadStaticHandleBucket *pNext, DWORD Size, BaseDomain *pDomain); + ~ThreadStaticHandleBucket(); + + // This returns the next bucket. + ThreadStaticHandleBucket *GetNext() + { + LIMITED_METHOD_CONTRACT; + + return m_pNext; + } + + // Allocate handles from the bucket. + OBJECTHANDLE GetHandles(); + +private: + ThreadStaticHandleBucket *m_pNext; + int m_ArraySize; + OBJECTHANDLE m_hndHandleArray; +}; + + +// The large heap handle table is used to allocate handles that are pointers +// to objects stored in an array in the large object heap. +class ThreadStaticHandleTable +{ +public: + // Constructor and desctructor. + ThreadStaticHandleTable(BaseDomain *pDomain); + ~ThreadStaticHandleTable(); + + // Allocate handles from the large heap handle table. + OBJECTHANDLE AllocateHandles(DWORD nRequested); + +private: + // The buckets of object handles. + ThreadStaticHandleBucket *m_pHead; + + // We need to know the containing domain so we know where to allocate handles + BaseDomain *m_pDomain; +}; + + + + +//-------------------------------------------------------------------------------------- +// Base class for domains. It provides an abstract way of finding the first assembly and +// for creating assemblies in the the domain. The system domain only has one assembly, it +// contains the classes that are logically shared between domains. All other domains can +// have multiple assemblies. Iteration is done be getting the first assembly and then +// calling the Next() method on the assembly. +// +// The system domain should be as small as possible, it includes object, exceptions, etc. +// which are the basic classes required to load other assemblies. All other classes +// should be loaded into the domain. Of coarse there is a trade off between loading the +// same classes multiple times, requiring all domains to load certain assemblies (working +// set) and being able to specify specific versions. +// + +#define LOW_FREQUENCY_HEAP_RESERVE_SIZE (3 * PAGE_SIZE) +#define LOW_FREQUENCY_HEAP_COMMIT_SIZE (1 * PAGE_SIZE) + +#define HIGH_FREQUENCY_HEAP_RESERVE_SIZE (10 * PAGE_SIZE) +#define HIGH_FREQUENCY_HEAP_COMMIT_SIZE (1 * PAGE_SIZE) + +#define STUB_HEAP_RESERVE_SIZE (3 * PAGE_SIZE) +#define STUB_HEAP_COMMIT_SIZE (1 * PAGE_SIZE) + +// -------------------------------------------------------------------------------- +// PE File List lock - for creating list locks on PE files +// -------------------------------------------------------------------------------- + +class PEFileListLock : public ListLock +{ +public: +#ifndef DACCESS_COMPILE + ListLockEntry *FindFileLock(PEFile *pFile) + { + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + PRECONDITION(HasLock()); + + ListLockEntry *pEntry; + + for (pEntry = m_pHead; + pEntry != NULL; + pEntry = pEntry->m_pNext) + { + if (((PEFile *)pEntry->m_pData)->Equals(pFile)) + { + return pEntry; + } + } + + return NULL; + } +#endif // DACCESS_COMPILE + + DEBUG_NOINLINE static void HolderEnter(PEFileListLock *pThis) PUB + { + WRAPPER_NO_CONTRACT; + ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT; + + pThis->Enter(); + } + + DEBUG_NOINLINE static void HolderLeave(PEFileListLock *pThis) PUB + { + WRAPPER_NO_CONTRACT; + ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT; + + pThis->Leave(); + } + + typedef Wrapper Holder; +}; + +typedef PEFileListLock::Holder PEFileListLockHolder; + +// Loading infrastructure: +// +// a DomainFile is a file being loaded. Files are loaded in layers to enable loading in the +// presence of dependency loops. +// +// FileLoadLevel describes the various levels available. These are implemented slightly +// differently for assemblies and modules, but the basic structure is the same. +// +// LoadLock and FileLoadLock form the ListLock data structures for files. The FileLoadLock +// is specialized in that it allows taking a lock at a particular level. Basicall any +// thread may obtain the lock at a level at which the file has previously been loaded to, but +// only one thread may obtain the lock at its current level. +// +// The PendingLoadQueue is a per thread data structure which serves two purposes. First, it +// holds a "load limit" which automatically restricts the level of recursive loads to be +// one less than the current load which is preceding. This, together with the AppDomain +// LoadLock level behavior, will prevent any deadlocks from occuring due to circular +// dependencies. (Note that it is important that the loading logic understands this restriction, +// and any given level of loading must deal with the fact that any recursive loads will be partially +// unfulfilled in a specific way.) +// +// The second function is to queue up any unfulfilled load requests for the thread. These +// are then delivered immediately after the current load request is dealt with. + +class FileLoadLock : public ListLockEntry +{ +private: + FileLoadLevel m_level; + DomainFile *m_pDomainFile; + HRESULT m_cachedHR; + ADID m_AppDomainId; + +public: + static FileLoadLock *Create(PEFileListLock *pLock, PEFile *pFile, DomainFile *pDomainFile); + + ~FileLoadLock(); + DomainFile *GetDomainFile(); + ADID GetAppDomainId(); + FileLoadLevel GetLoadLevel(); + + // CanAcquire will return FALSE if Acquire will definitely not take the lock due + // to levels or deadlock. + // (Note that there is a race exiting from the function, where Acquire may end + // up not taking the lock anyway if another thread did work in the meantime.) + BOOL CanAcquire(FileLoadLevel targetLevel); + + // Acquire will return FALSE and not take the lock if the file + // has already been loaded to the target level. Otherwise, + // it will return TRUE and take the lock. + // + // Note that the taker must release the lock via IncrementLoadLevel. + BOOL Acquire(FileLoadLevel targetLevel); + + // CompleteLoadLevel can be called after Acquire returns true + // returns TRUE if it updated load level, FALSE if the level was set already + BOOL CompleteLoadLevel(FileLoadLevel level, BOOL success); + + void SetError(Exception *ex); + + void AddRef(); + UINT32 Release() DAC_EMPTY_RET(0); + +private: + + FileLoadLock(PEFileListLock *pLock, PEFile *pFile, DomainFile *pDomainFile); + + static void HolderLeave(FileLoadLock *pThis); + +public: + typedef Wrapper Holder; + +}; + +typedef FileLoadLock::Holder FileLoadLockHolder; + +#ifndef DACCESS_COMPILE + typedef ReleaseHolder FileLoadLockRefHolder; +#endif // DACCESS_COMPILE + + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning (disable: 4324) //sometimes 64bit compilers complain about alignment +#endif +class LoadLevelLimiter +{ + FileLoadLevel m_currentLevel; + LoadLevelLimiter* m_previousLimit; + BOOL m_bActive; + +public: + + LoadLevelLimiter() + : m_currentLevel(FILE_ACTIVE), + m_previousLimit(NULL), + m_bActive(FALSE) + { + LIMITED_METHOD_CONTRACT; + } + + void Activate() + { + WRAPPER_NO_CONTRACT; + m_previousLimit=GetThread()->GetLoadLevelLimiter(); + if(m_previousLimit) + m_currentLevel=m_previousLimit->GetLoadLevel(); + GetThread()->SetLoadLevelLimiter(this); + m_bActive=TRUE; + } + + void Deactivate() + { + WRAPPER_NO_CONTRACT; + if (m_bActive) + { + GetThread()->SetLoadLevelLimiter(m_previousLimit); + m_bActive=FALSE; + } + } + + ~LoadLevelLimiter() + { + WRAPPER_NO_CONTRACT; + + // PendingLoadQueues are allocated on the stack during a load, and + // shared with all nested loads on the same thread. + + // Make sure the thread pointer gets reset after the + // top level queue goes out of scope. + if(m_bActive) + { + Deactivate(); + } + } + + FileLoadLevel GetLoadLevel() + { + LIMITED_METHOD_CONTRACT; + return m_currentLevel; + } + + void SetLoadLevel(FileLoadLevel level) + { + LIMITED_METHOD_CONTRACT; + m_currentLevel = level; + } +}; +#ifdef _MSC_VER +#pragma warning (pop) //4324 +#endif + +#define OVERRIDE_LOAD_LEVEL_LIMIT(newLimit) \ + LoadLevelLimiter __newLimit; \ + __newLimit.Activate(); \ + __newLimit.SetLoadLevel(newLimit); + +// A BaseDomain much basic information in a code:AppDomain including +// +// * code:#AppdomainHeaps - Heaps for any data structures that will be freed on appdomain unload +// +class BaseDomain +{ + friend class Assembly; + friend class AssemblySpec; + friend class AppDomain; + friend class AppDomainNative; + + VPTR_BASE_VTABLE_CLASS(BaseDomain) + VPTR_UNIQUE(VPTR_UNIQUE_BaseDomain) + +protected: + // These 2 variables are only used on the AppDomain, but by placing them here + // we reduce the cost of keeping the asmconstants file up to date. + + // The creation sequence number of this app domain (starting from 1) + // This ID is generated by the code:SystemDomain::GetNewAppDomainId routine + // The ID are recycled. + // + // see also code:ADID + ADID m_dwId; + + DomainLocalBlock m_sDomainLocalBlock; + +public: + + class AssemblyIterator; + friend class AssemblyIterator; + + // Static initialization. + static void Attach(); + + //**************************************************************************************** + // + // Initialization/shutdown routines for every instance of an BaseDomain. + + BaseDomain(); + virtual ~BaseDomain() {} + void Init(); + void Stop(); + void Terminate(); + + // ID to uniquely identify this AppDomain - used by the AppDomain publishing + // service (to publish the list of all appdomains present in the process), + // which in turn is used by, for eg., the debugger (to decide which App- + // Domain(s) to attach to). + // This is also used by Remoting for routing cross-appDomain calls. + ADID GetId (void) + { + LIMITED_METHOD_DAC_CONTRACT; + STATIC_CONTRACT_SO_TOLERANT; + return m_dwId; + } + + virtual BOOL IsAppDomain() { LIMITED_METHOD_DAC_CONTRACT; return FALSE; } + virtual BOOL IsSharedDomain() { LIMITED_METHOD_DAC_CONTRACT; return FALSE; } + + inline BOOL IsDefaultDomain(); // defined later in this file + virtual PTR_LoaderAllocator GetLoaderAllocator() = 0; + virtual PTR_AppDomain AsAppDomain() + { + LIMITED_METHOD_CONTRACT; + STATIC_CONTRACT_SO_TOLERANT; + _ASSERTE(!"Not an AppDomain"); + return NULL; + } + + + // If one domain is the SharedDomain and one is an AppDomain then + // return the AppDomain, i.e. return the domain with the shorter lifetime + // of the two given domains. + static PTR_BaseDomain ComputeBaseDomain( + BaseDomain *pGenericDefinitionDomain, // the domain that owns the generic type or method + Instantiation classInst, // the type arguments to the type (if any) + Instantiation methodInst = Instantiation()); // the type arguments to the method (if any) + + static PTR_BaseDomain ComputeBaseDomain(TypeKey * pTypeKey); + +#ifdef FEATURE_COMINTEROP + //**************************************************************************************** + // + // This will look up interop data for a method table + // + +#ifndef DACCESS_COMPILE + // Returns the data pointer if present, NULL otherwise + InteropMethodTableData *LookupComInteropData(MethodTable *pMT) + { + // Take the lock + CrstHolder holder(&m_InteropDataCrst); + + // Lookup + InteropMethodTableData *pData = (InteropMethodTableData*) m_interopDataHash.LookupValue((UPTR) pMT, (LPVOID) NULL); + + // Not there... + if (pData == (InteropMethodTableData*) INVALIDENTRY) + return NULL; + + // Found it + return pData; + } + + // Returns TRUE if successfully inserted, FALSE if this would be a duplicate entry + BOOL InsertComInteropData(MethodTable* pMT, InteropMethodTableData *pData) + { + // We don't keep track of this kind of information for interfaces + _ASSERTE(!pMT->IsInterface()); + + // Take the lock + CrstHolder holder(&m_InteropDataCrst); + + // Check to see that it's not already in there + InteropMethodTableData *pDupData = (InteropMethodTableData*) m_interopDataHash.LookupValue((UPTR) pMT, (LPVOID) NULL); + if (pDupData != (InteropMethodTableData*) INVALIDENTRY) + return FALSE; + + // Not in there, so insert + m_interopDataHash.InsertValue((UPTR) pMT, (LPVOID) pData); + + // Success + return TRUE; + } +#endif // DACCESS_COMPILE +#endif // FEATURE_COMINTEROP + + void SetDisableInterfaceCache() + { + m_fDisableInterfaceCache = TRUE; + } + BOOL GetDisableInterfaceCache() + { + return m_fDisableInterfaceCache; + } + +#ifdef FEATURE_COMINTEROP + MngStdInterfacesInfo * GetMngStdInterfacesInfo() + { + LIMITED_METHOD_CONTRACT; + + return m_pMngStdInterfacesInfo; + } + + PTR_CLRPrivBinderWinRT GetWinRtBinder() + { + return m_pWinRtBinder; + } +#endif // FEATURE_COMINTEROP + + //**************************************************************************************** + // This method returns marshaling data that the EE uses that is stored on a per app domain + // basis. + EEMarshalingData *GetMarshalingData(); + + // Deletes marshaling data at shutdown (which contains cached factories that needs to be released) + void DeleteMarshalingData(); + +#ifdef _DEBUG + BOOL OwnDomainLocalBlockLock() + { + WRAPPER_NO_CONTRACT; + + return m_DomainLocalBlockCrst.OwnedByCurrentThread(); + } +#endif + + //**************************************************************************************** + // Get the class init lock. The method is limited to friends because inappropriate use + // will cause deadlocks in the system + ListLock* GetClassInitLock() + { + LIMITED_METHOD_CONTRACT; + + return &m_ClassInitLock; + } + + ListLock* GetJitLock() + { + LIMITED_METHOD_CONTRACT; + return &m_JITLock; + } + + ListLock* GetILStubGenLock() + { + LIMITED_METHOD_CONTRACT; + return &m_ILStubGenLock; + } + + STRINGREF *IsStringInterned(STRINGREF *pString); + STRINGREF *GetOrInternString(STRINGREF *pString); + + virtual BOOL CanUnload() { LIMITED_METHOD_CONTRACT; return FALSE; } // can never unload BaseDomain + + // Returns an array of OBJECTREF* that can be used to store domain specific data. + // Statics and reflection info (Types, MemberInfo,..) are stored this way + // If ppLazyAllocate != 0, allocation will only take place if *ppLazyAllocate != 0 (and the allocation + // will be properly serialized) + OBJECTREF *AllocateObjRefPtrsInLargeTable(int nRequested, OBJECTREF** ppLazyAllocate = NULL, BOOL bCrossAD = FALSE); + +#ifdef FEATURE_PREJIT + // Ensures that the file for logging profile data is open (we only open it once) + // return false on failure + static BOOL EnsureNGenLogFileOpen(); +#endif + + //**************************************************************************************** + // Handles + +#if !defined(DACCESS_COMPILE) && !defined(CROSSGEN_COMPILE) // needs GetCurrentThreadHomeHeapNumber + OBJECTHANDLE CreateTypedHandle(OBJECTREF object, int type) + { + WRAPPER_NO_CONTRACT; + return ::CreateTypedHandle(m_hHandleTableBucket->pTable[GetCurrentThreadHomeHeapNumber()], object, type); + } + + OBJECTHANDLE CreateHandle(OBJECTREF object) + { + WRAPPER_NO_CONTRACT; + CONDITIONAL_CONTRACT_VIOLATION(ModeViolation, object == NULL) + return ::CreateHandle(m_hHandleTableBucket->pTable[GetCurrentThreadHomeHeapNumber()], object); + } + + OBJECTHANDLE CreateWeakHandle(OBJECTREF object) + { + WRAPPER_NO_CONTRACT; + return ::CreateWeakHandle(m_hHandleTableBucket->pTable[GetCurrentThreadHomeHeapNumber()], object); + } + + OBJECTHANDLE CreateShortWeakHandle(OBJECTREF object) + { + WRAPPER_NO_CONTRACT; + return ::CreateShortWeakHandle(m_hHandleTableBucket->pTable[GetCurrentThreadHomeHeapNumber()], object); + } + + OBJECTHANDLE CreateLongWeakHandle(OBJECTREF object) + { + WRAPPER_NO_CONTRACT; + CONDITIONAL_CONTRACT_VIOLATION(ModeViolation, object == NULL) + return ::CreateLongWeakHandle(m_hHandleTableBucket->pTable[GetCurrentThreadHomeHeapNumber()], object); + } + + OBJECTHANDLE CreateStrongHandle(OBJECTREF object) + { + WRAPPER_NO_CONTRACT; + return ::CreateStrongHandle(m_hHandleTableBucket->pTable[GetCurrentThreadHomeHeapNumber()], object); + } + + OBJECTHANDLE CreatePinningHandle(OBJECTREF object) + { + WRAPPER_NO_CONTRACT; +#if CHECK_APP_DOMAIN_LEAKS + if(IsAppDomain()) + object->TryAssignAppDomain((AppDomain*)this,TRUE); +#endif + return ::CreatePinningHandle(m_hHandleTableBucket->pTable[GetCurrentThreadHomeHeapNumber()], object); + } + + OBJECTHANDLE CreateSizedRefHandle(OBJECTREF object) + { + WRAPPER_NO_CONTRACT; + OBJECTHANDLE h = ::CreateSizedRefHandle( + m_hHandleTableBucket->pTable[GCHeap::IsServerHeap() ? (m_dwSizedRefHandles % m_iNumberOfProcessors) : GetCurrentThreadHomeHeapNumber()], + object); + InterlockedIncrement((LONG*)&m_dwSizedRefHandles); + return h; + } + +#ifdef FEATURE_COMINTEROP + OBJECTHANDLE CreateRefcountedHandle(OBJECTREF object) + { + WRAPPER_NO_CONTRACT; + return ::CreateRefcountedHandle(m_hHandleTableBucket->pTable[GetCurrentThreadHomeHeapNumber()], object); + } + + OBJECTHANDLE CreateWinRTWeakHandle(OBJECTREF object, IWeakReference* pWinRTWeakReference) + { + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + return ::CreateWinRTWeakHandle(m_hHandleTableBucket->pTable[GetCurrentThreadHomeHeapNumber()], object, pWinRTWeakReference); + } +#endif // FEATURE_COMINTEROP + + OBJECTHANDLE CreateVariableHandle(OBJECTREF object, UINT type) + { + WRAPPER_NO_CONTRACT; + return ::CreateVariableHandle(m_hHandleTableBucket->pTable[GetCurrentThreadHomeHeapNumber()], object, type); + } + + OBJECTHANDLE CreateDependentHandle(OBJECTREF primary, OBJECTREF secondary) + { + WRAPPER_NO_CONTRACT; + return ::CreateDependentHandle(m_hHandleTableBucket->pTable[GetCurrentThreadHomeHeapNumber()], primary, secondary); + } +#endif // DACCESS_COMPILE && !CROSSGEN_COMPILE + + BOOL ContainsOBJECTHANDLE(OBJECTHANDLE handle); + +#ifdef FEATURE_FUSION + IApplicationContext *GetFusionContext() {LIMITED_METHOD_CONTRACT; return m_pFusionContext; } +#else + IUnknown *GetFusionContext() {LIMITED_METHOD_CONTRACT; return m_pFusionContext; } + +#if defined(FEATURE_HOST_ASSEMBLY_RESOLVER) + CLRPrivBinderCoreCLR *GetTPABinderContext() {LIMITED_METHOD_CONTRACT; return m_pTPABinderContext; } +#endif // defined(FEATURE_HOST_ASSEMBLY_RESOLVER) + +#endif + + CrstExplicitInit * GetLoaderAllocatorReferencesLock() + { + LIMITED_METHOD_CONTRACT; + return &m_crstLoaderAllocatorReferences; + } + +protected: + + //**************************************************************************************** + // Helper method to initialize the large heap handle table. + void InitLargeHeapHandleTable(); + + //**************************************************************************************** + // Adds an assembly to the domain. + void AddAssemblyNoLock(Assembly* assem); + + //**************************************************************************************** + // + // Hash table that maps a MethodTable to COM Interop compatibility data. + PtrHashMap m_interopDataHash; + + // Critical sections & locks + PEFileListLock m_FileLoadLock; // Protects the list of assemblies in the domain + CrstExplicitInit m_DomainCrst; // General Protection for the Domain + CrstExplicitInit m_DomainCacheCrst; // Protects the Assembly and Unmanaged caches + CrstExplicitInit m_DomainLocalBlockCrst; + CrstExplicitInit m_InteropDataCrst; // Used for COM Interop compatiblilty + // Used to protect the reference lists in the collectible loader allocators attached to this appdomain + CrstExplicitInit m_crstLoaderAllocatorReferences; + CrstExplicitInit m_WinRTFactoryCacheCrst; // For WinRT factory cache + + //#AssemblyListLock + // Used to protect the assembly list. Taken also by GC or debugger thread, therefore we have to avoid + // triggering GC while holding this lock (by switching the thread to GC_NOTRIGGER while it is held). + CrstExplicitInit m_crstAssemblyList; + BOOL m_fDisableInterfaceCache; // RCW COM interface cache + ListLock m_ClassInitLock; + ListLock m_JITLock; + ListLock m_ILStubGenLock; + + // Fusion context, used for adding assemblies to the is domain. It defines + // fusion properties for finding assemblyies such as SharedBinPath, + // PrivateBinPath, Application Directory, etc. +#ifdef FEATURE_FUSION + IApplicationContext* m_pFusionContext; // Binding context for the domain +#else + IUnknown *m_pFusionContext; // Current binding context for the domain + +#if defined(FEATURE_HOST_ASSEMBLY_RESOLVER) + CLRPrivBinderCoreCLR *m_pTPABinderContext; // Reference to the binding context that holds TPA list details +#endif // defined(FEATURE_HOST_ASSEMBLY_RESOLVER) + +#endif + + HandleTableBucket *m_hHandleTableBucket; + + // The large heap handle table. + LargeHeapHandleTable *m_pLargeHeapHandleTable; + + // The large heap handle table critical section. + CrstExplicitInit m_LargeHeapHandleTableCrst; + + EEMarshalingData *m_pMarshalingData; + +#ifdef FEATURE_COMINTEROP + // Information regarding the managed standard interfaces. + MngStdInterfacesInfo *m_pMngStdInterfacesInfo; + + // WinRT binder (only in classic = non-AppX; AppX has the WinRT binder inside code:CLRPrivBinderAppX) + PTR_CLRPrivBinderWinRT m_pWinRtBinder; +#endif // FEATURE_COMINTEROP + + // Number of allocated slots for context local statics of this domain + DWORD m_dwContextStatics; + + // Protects allocation of slot IDs for thread and context statics + static CrstStatic m_SpecialStaticsCrst; + +public: + // Lazily allocate offset for context static + DWORD AllocateContextStaticsOffset(DWORD* pOffsetSlot); + +public: + // Only call this routine when you can guarantee there are no + // loads in progress. + void ClearFusionContext(); + +public: + + //**************************************************************************************** + // Synchronization holders. + + class LockHolder : public CrstHolder + { + public: + LockHolder(BaseDomain *pD) + : CrstHolder(&pD->m_DomainCrst) + { + WRAPPER_NO_CONTRACT; + } + }; + friend class LockHolder; + + class CacheLockHolder : public CrstHolder + { + public: + CacheLockHolder(BaseDomain *pD) + : CrstHolder(&pD->m_DomainCacheCrst) + { + WRAPPER_NO_CONTRACT; + } + }; + friend class CacheLockHolder; + + class DomainLocalBlockLockHolder : public CrstHolder + { + public: + DomainLocalBlockLockHolder(BaseDomain *pD) + : CrstHolder(&pD->m_DomainLocalBlockCrst) + { + WRAPPER_NO_CONTRACT; + } + }; + friend class DomainLocalBlockLockHolder; + + class LoadLockHolder : public PEFileListLockHolder + { + public: + LoadLockHolder(BaseDomain *pD, BOOL Take = TRUE) + : PEFileListLockHolder(&pD->m_FileLoadLock, Take) + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + CAN_TAKE_LOCK; + } + CONTRACTL_END; + } + }; + friend class LoadLockHolder; + class WinRTFactoryCacheLockHolder : public CrstHolder + { + public: + WinRTFactoryCacheLockHolder(BaseDomain *pD) + : CrstHolder(&pD->m_WinRTFactoryCacheCrst) + { + WRAPPER_NO_CONTRACT; + } + }; + friend class WinRTFactoryCacheLockHolder; + +public: + void InitVSD(); + RangeList *GetCollectibleVSDRanges() { return &m_collVSDRanges; } + +private: + TypeIDMap m_typeIDMap; + // Range list for collectible types. Maps VSD PCODEs back to the VirtualCallStubManager they belong to + LockedRangeList m_collVSDRanges; + +public: + UINT32 GetTypeID(PTR_MethodTable pMT); + UINT32 LookupTypeID(PTR_MethodTable pMT); + PTR_MethodTable LookupType(UINT32 id); + +private: + // I have yet to figure out an efficent way to get the number of handles + // of a particular type that's currently used by the process without + // spending more time looking at the handle table code. We know that + // our only customer (asp.net) in Dev10 is not going to create many of + // these handles so I am taking a shortcut for now and keep the sizedref + // handle count on the AD itself. + DWORD m_dwSizedRefHandles; + + static int m_iNumberOfProcessors; + +public: + // Called by DestroySizedRefHandle + void DecNumSizedRefHandles() + { + WRAPPER_NO_CONTRACT; + LONG result; + result = InterlockedDecrement((LONG*)&m_dwSizedRefHandles); + _ASSERTE(result >= 0); + } + + DWORD GetNumSizedRefHandles() + { + return m_dwSizedRefHandles; + } + + // Profiler rejit +private: + ReJitManager m_reJitMgr; + +public: + ReJitManager * GetReJitManager() { return &m_reJitMgr; } + +#ifdef DACCESS_COMPILE +public: + virtual void EnumMemoryRegions(CLRDataEnumMemoryFlags flags, + bool enumThis); +#endif + +}; // class BaseDomain + +enum +{ + ATTACH_ASSEMBLY_LOAD = 0x1, + ATTACH_MODULE_LOAD = 0x2, + ATTACH_CLASS_LOAD = 0x4, + + ATTACH_ALL = 0x7 +}; + +class ADUnloadSink +{ + +protected: + ~ADUnloadSink(); + CLREvent m_UnloadCompleteEvent; + HRESULT m_UnloadResult; + Volatile m_cRef; +public: + ADUnloadSink(); + void ReportUnloadResult (HRESULT hr, OBJECTREF* pException); + void WaitUnloadCompletion(); + HRESULT GetUnloadResult() {LIMITED_METHOD_CONTRACT; return m_UnloadResult;}; + void Reset(); + ULONG AddRef(); + ULONG Release(); +}; + + +FORCEINLINE void ADUnloadSink__Release(ADUnloadSink* pADSink) +{ + WRAPPER_NO_CONTRACT; + + if (pADSink) + pADSink->Release(); +} + +typedef Wrapper ADUnloadSinkHolder; + +// This filters the output of IterateAssemblies. This ought to be declared more locally +// but it would result in really verbose callsites. +// +// Assemblies can be categorized by their load status (loaded, loading, or loaded just +// enough that they would be made available to profilers) +// Independently, they can also be categorized as execution or introspection. +// +// An assembly will be included in the results of IterateAssemblies only if +// the appropriate bit is set for *both* characterizations. +// +// The flags can be combined so if you want all loaded assemblies, you must specify: +// +/// kIncludeLoaded|kIncludeExecution|kIncludeIntrospection + +enum AssemblyIterationFlags +{ + // load status flags + kIncludeLoaded = 0x00000001, // include assemblies that are already loaded + // (m_level >= code:FILE_LOAD_DELIVER_EVENTS) + kIncludeLoading = 0x00000002, // include assemblies that are still in the process of loading + // (all m_level values) + kIncludeAvailableToProfilers + = 0x00000020, // include assemblies available to profilers + // See comment at code:DomainFile::IsAvailableToProfilers + + // Execution / introspection flags + kIncludeExecution = 0x00000004, // include assemblies that are loaded for execution only + kIncludeIntrospection = 0x00000008, // include assemblies that are loaded for introspection only + + kIncludeFailedToLoad = 0x00000010, // include assemblies that failed to load + + // Collectible assemblies flags + kExcludeCollectible = 0x00000040, // Exclude all collectible assemblies + kIncludeCollected = 0x00000080, + // Include assemblies which were collected and cannot be referenced anymore. Such assemblies are not + // AddRef-ed. Any manipulation with them should be protected by code:GetAssemblyListLock. + // Should be used only by code:LoaderAllocator::GCLoaderAllocators. + +}; // enum AssemblyIterationFlags + +//--------------------------------------------------------------------------------------- +// +// Base class for holder code:CollectibleAssemblyHolder (see code:HolderBase). +// Manages AddRef/Release for collectible assemblies. It is no-op for 'normal' non-collectible assemblies. +// +// Each type of type parameter needs 2 methods implemented: +// code:CollectibleAssemblyHolderBase::GetLoaderAllocator +// code:CollectibleAssemblyHolderBase::IsCollectible +// +template +class CollectibleAssemblyHolderBase +{ +protected: + _Type m_value; +public: + CollectibleAssemblyHolderBase(const _Type & value = NULL) + { + LIMITED_METHOD_CONTRACT; + m_value = value; + } + void DoAcquire() + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // We don't need to keep the assembly alive in DAC - see code:#CAH_DAC +#ifndef DACCESS_COMPILE + if (this->IsCollectible(m_value)) + { + LoaderAllocator * pLoaderAllocator = GetLoaderAllocator(m_value); + pLoaderAllocator->AddReference(); + } +#endif //!DACCESS_COMPILE + } + void DoRelease() + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + +#ifndef DACCESS_COMPILE + if (this->IsCollectible(m_value)) + { + LoaderAllocator * pLoaderAllocator = GetLoaderAllocator(m_value); + pLoaderAllocator->Release(); + } +#endif //!DACCESS_COMPILE + } + +private: + LoaderAllocator * GetLoaderAllocator(DomainAssembly * pDomainAssembly) + { + WRAPPER_NO_CONTRACT; + return pDomainAssembly->GetLoaderAllocator(); + } + BOOL IsCollectible(DomainAssembly * pDomainAssembly) + { + WRAPPER_NO_CONTRACT; + return pDomainAssembly->IsCollectible(); + } + LoaderAllocator * GetLoaderAllocator(Assembly * pAssembly) + { + WRAPPER_NO_CONTRACT; + return pAssembly->GetLoaderAllocator(); + } + BOOL IsCollectible(Assembly * pAssembly) + { + WRAPPER_NO_CONTRACT; + return pAssembly->IsCollectible(); + } +}; // class CollectibleAssemblyHolderBase<> + +//--------------------------------------------------------------------------------------- +// +// Holder of assembly reference which keeps collectible assembly alive while the holder is valid. +// +// Collectible assembly can be collected at any point when GC happens. Almost instantly all native data +// structures of the assembly (e.g. code:DomainAssembly, code:Assembly) could be deallocated. +// Therefore any usage of (collectible) assembly data structures from native world, has to prevent the +// deallocation by increasing ref-count on the assembly / associated loader allocator. +// +// #CAH_DAC +// In DAC we don't AddRef/Release as the assembly doesn't have to be kept alive: The process is stopped when +// DAC is used and therefore the assembly cannot just disappear. +// +template +class CollectibleAssemblyHolder : public BaseWrapper<_Type, CollectibleAssemblyHolderBase<_Type> > +{ +public: + FORCEINLINE + CollectibleAssemblyHolder(const _Type & value = NULL, BOOL fTake = TRUE) + : BaseWrapper<_Type, CollectibleAssemblyHolderBase<_Type> >(value, fTake) + { + STATIC_CONTRACT_WRAPPER; + } + + FORCEINLINE + CollectibleAssemblyHolder & + operator=(const _Type & value) + { + STATIC_CONTRACT_WRAPPER; + BaseWrapper<_Type, CollectibleAssemblyHolderBase<_Type> >::operator=(value); + return *this; + } + + // Operator & is overloaded in parent, therefore we have to get to 'this' pointer explicitly. + FORCEINLINE + CollectibleAssemblyHolder<_Type> * + This() + { + LIMITED_METHOD_CONTRACT; + return this; + } +}; // class CollectibleAssemblyHolder<> + +//--------------------------------------------------------------------------------------- +// +#ifdef FEATURE_LOADER_OPTIMIZATION +class SharedAssemblyLocator +{ +public: + enum + { + DOMAINASSEMBLY = 1, + PEASSEMBLY = 2, + PEASSEMBLYEXACT = 3 + }; + DWORD GetType() {LIMITED_METHOD_CONTRACT; return m_type;}; +#ifndef DACCESS_COMPILE + DomainAssembly* GetDomainAssembly() {LIMITED_METHOD_CONTRACT; _ASSERTE(m_type==DOMAINASSEMBLY); return (DomainAssembly*)m_value;}; + PEAssembly* GetPEAssembly() {LIMITED_METHOD_CONTRACT; _ASSERTE(m_type==PEASSEMBLY||m_type==PEASSEMBLYEXACT); return (PEAssembly*)m_value;}; + SharedAssemblyLocator(DomainAssembly* pAssembly) + { + LIMITED_METHOD_CONTRACT; + m_type=DOMAINASSEMBLY; + m_value=pAssembly; + } + SharedAssemblyLocator(PEAssembly* pFile, DWORD type = PEASSEMBLY) + { + LIMITED_METHOD_CONTRACT; + m_type = type; + m_value = pFile; + } +#endif // DACCESS_COMPILE + + DWORD Hash(); +protected: + DWORD m_type; + LPVOID m_value; +#if FEATURE_VERSIONING + ULONG m_uIdentityHash; +#endif +}; +#endif // FEATURE_LOADER_OPTIMIZATION + +// +// Stores binding information about failed assembly loads for DAC +// +struct FailedAssembly { + SString displayName; + SString location; +#ifdef FEATURE_FUSION + LOADCTX_TYPE context; +#endif + HRESULT error; + + void Initialize(AssemblySpec *pSpec, Exception *ex) + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + displayName.SetASCII(pSpec->GetName()); + location.Set(pSpec->GetCodeBase()); + error = ex->GetHR(); + + // + // Determine the binding context assembly would have been in. + // If the parent has been set, use its binding context. + // If the parent hasn't been set but the code base has, use LoadFrom. + // Otherwise, use the default. + // +#ifdef FEATURE_FUSION + context = pSpec->GetParentIAssembly() ? pSpec->GetParentIAssembly()->GetFusionLoadContext() : LOADCTX_TYPE_LOADFROM; +#endif // FEATURE_FUSION + } +}; + +#ifdef FEATURE_COMINTEROP + +// Cache used by COM Interop +struct NameToTypeMapEntry +{ + // Host space representation of the key + struct Key + { + LPCWSTR m_wzName; // The type name or registry string representation of the GUID "{}" + SIZE_T m_cchName; // wcslen(m_wzName) for faster hashtable lookup + }; + struct DacKey + { + PTR_CWSTR m_wzName; // The type name or registry string representation of the GUID "{}" + SIZE_T m_cchName; // wcslen(m_wzName) for faster hashtable lookup + } m_key; + TypeHandle m_typeHandle; // Using TypeHandle instead of MethodTable* to avoid losing information when sharing method tables. + UINT m_nEpoch; // tracks creation Epoch. This is incremented each time an external reader enumerate the cache + BYTE m_bFlags; +}; + +typedef DPTR(NameToTypeMapEntry) PTR_NameToTypeMapEntry; + +class NameToTypeMapTraits : public NoRemoveSHashTraits< DefaultSHashTraits > +{ +public: + typedef NameToTypeMapEntry::Key key_t; + + static const NameToTypeMapEntry Null() { NameToTypeMapEntry e; e.m_key.m_wzName = NULL; e.m_key.m_cchName = 0; return e; } + static bool IsNull(const NameToTypeMapEntry &e) { return e.m_key.m_wzName == NULL; } + static const key_t GetKey(const NameToTypeMapEntry &e) + { + key_t key; + key.m_wzName = (LPCWSTR)(e.m_key.m_wzName); // this cast brings the string over to the host, in a DAC build + key.m_cchName = e.m_key.m_cchName; + + return key; + } + static count_t Hash(const key_t &key) { WRAPPER_NO_CONTRACT; return HashStringN(key.m_wzName, key.m_cchName); } + + static BOOL Equals(const key_t &lhs, const key_t &rhs) + { + WRAPPER_NO_CONTRACT; + return (lhs.m_cchName == rhs.m_cchName) && memcmp(lhs.m_wzName, rhs.m_wzName, lhs.m_cchName * sizeof(WCHAR)) == 0; + } + + void OnDestructPerEntryCleanupAction(const NameToTypeMapEntry& e) + { + WRAPPER_NO_CONTRACT; + _ASSERTE(e.m_key.m_cchName == wcslen(e.m_key.m_wzName)); +#ifndef DACCESS_COMPILE + delete [] e.m_key.m_wzName; +#endif // DACCESS_COMPILE + } + static const bool s_DestructPerEntryCleanupAction = true; +}; + +typedef SHash NameToTypeMapTable; + +typedef DPTR(NameToTypeMapTable) PTR_NameToTypeMapTable; + +struct WinRTFactoryCacheEntry +{ + typedef MethodTable *Key; + Key key; // Type as KEY + + CtxEntry *m_pCtxEntry; // Context entry - used to verify whether the cache is a match + OBJECTHANDLE m_ohFactoryObject; // Handle to factory object +}; + +class WinRTFactoryCacheTraits : public DefaultSHashTraits +{ +public: + typedef WinRTFactoryCacheEntry::Key key_t; + static const WinRTFactoryCacheEntry Null() { WinRTFactoryCacheEntry e; e.key = NULL; return e; } + static bool IsNull(const WinRTFactoryCacheEntry &e) { return e.key == NULL; } + static const WinRTFactoryCacheEntry::Key GetKey(const WinRTFactoryCacheEntry& e) { return e.key; } + static count_t Hash(WinRTFactoryCacheEntry::Key key) { return (count_t)((size_t)key); } + static BOOL Equals(WinRTFactoryCacheEntry::Key lhs, WinRTFactoryCacheEntry::Key rhs) + { return lhs == rhs; } + static const WinRTFactoryCacheEntry Deleted() { WinRTFactoryCacheEntry e; e.key = (MethodTable *)-1; return e; } + static bool IsDeleted(const WinRTFactoryCacheEntry &e) { return e.key == (MethodTable *)-1; } + + static void OnDestructPerEntryCleanupAction(const WinRTFactoryCacheEntry& e); + static const bool s_DestructPerEntryCleanupAction = true; +}; + +typedef SHash WinRTFactoryCache; + +#endif // FEATURE_COMINTEROP + +class AppDomainIterator; + +const DWORD DefaultADID = 1; + +template class AppDomainCreationHolder; + +// An Appdomain is the managed equivalent of a process. It is an isolation unit (conceptually you don't +// have pointers directly from one appdomain to another, but rather go through remoting proxies). It is +// also a unit of unloading. +// +// Threads are always running in the context of a particular AppDomain. See +// file:threads.h#RuntimeThreadLocals for more details. +// +// see code:BaseDomain for much of the meat of a AppDomain (heaps locks, etc) +// * code:AppDomain.m_Assemblies - is a list of code:Assembly in the appdomain +// +class AppDomain : public BaseDomain +{ + friend class ADUnloadSink; + friend class SystemDomain; + friend class AssemblySink; + friend class AppDomainNative; + friend class AssemblyNative; + friend class AssemblySpec; + friend class ClassLoader; + friend class ThreadNative; + friend class RCWCache; + friend class ClrDataAccess; + friend class CheckAsmOffsets; + friend class AppDomainFromIDHolder; + + VPTR_VTABLE_CLASS(AppDomain, BaseDomain) + +public: +#ifndef DACCESS_COMPILE + AppDomain(); + virtual ~AppDomain(); +#endif + static void DoADUnloadWork(); + DomainAssembly* FindDomainAssembly(Assembly*); + void EnterContext(Thread* pThread, Context* pCtx,ContextTransitionFrame *pFrame); + +#ifndef DACCESS_COMPILE + //----------------------------------------------------------------------------------------------------------------- + // Convenience wrapper for ::GetAppDomain to provide better encapsulation. + static AppDomain * GetCurrentDomain() + { return ::GetAppDomain(); } +#endif //!DACCESS_COMPILE + + //----------------------------------------------------------------------------------------------------------------- + // Initializes an AppDomain. (this functions is not called from the SystemDomain) + void Init(); + + // creates only unamaged part + static void CreateUnmanagedObject(AppDomainCreationHolder& result); + inline void SetAppDomainManagerInfo(LPCWSTR szAssemblyName, LPCWSTR szTypeName, EInitializeNewDomainFlags dwInitializeDomainFlags); + inline BOOL HasAppDomainManagerInfo(); + inline LPCWSTR GetAppDomainManagerAsm(); + inline LPCWSTR GetAppDomainManagerType(); + inline EInitializeNewDomainFlags GetAppDomainManagerInitializeNewDomainFlags(); + +#ifndef FEATURE_CORECLR + inline BOOL AppDomainManagerSetFromConfig(); + Assembly *GetAppDomainManagerEntryAssembly(); + void ComputeTargetFrameworkName(); +#endif // FEATURE_CORECLR + +#if defined(FEATURE_CORECLR) && defined(FEATURE_COMINTEROP) + HRESULT SetWinrtApplicationContext(SString &appLocalWinMD); +#endif // FEATURE_CORECLR && FEATURE_COMINTEROP + + BOOL CanReversePInvokeEnter(); + void SetReversePInvokeCannotEnter(); + bool MustForceTrivialWaitOperations(); + void SetForceTrivialWaitOperations(); + + //**************************************************************************************** + // + // Stop deletes all the assemblies but does not remove other resources like + // the critical sections + void Stop(); + + // Gets rid of resources + void Terminate(); + +#ifdef FEATURE_PREJIT + //assembly cleanup that requires suspended runtime + void DeleteNativeCodeRanges(); +#endif + + // final assembly cleanup + void ShutdownAssemblies(); + void ShutdownFreeLoaderAllocators(BOOL bFromManagedCode); + + void ReleaseDomainBoundInfo(); + void ReleaseFiles(); + + + // Remove the Appdomain for the system and cleans up. This call should not be + // called from shut down code. + void CloseDomain(); + + virtual BOOL IsAppDomain() { LIMITED_METHOD_DAC_CONTRACT; return TRUE; } + virtual PTR_AppDomain AsAppDomain() { LIMITED_METHOD_CONTRACT; return dac_cast(this); } + +#ifndef FEATURE_CORECLR + void InitializeSorting(OBJECTREF* ppAppdomainSetup); + void InitializeHashing(OBJECTREF* ppAppdomainSetup); +#endif + + OBJECTREF DoSetup(OBJECTREF* setupInfo); + + OBJECTREF GetExposedObject(); + OBJECTREF GetRawExposedObject() { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + MODE_COOPERATIVE; + } + CONTRACTL_END; + if (m_ExposedObject) { + return ObjectFromHandle(m_ExposedObject); + } + else { + return NULL; + } + } + + OBJECTHANDLE GetRawExposedObjectHandleForDebugger() { LIMITED_METHOD_DAC_CONTRACT; return m_ExposedObject; } + +#ifdef FEATURE_COMINTEROP + HRESULT GetComIPForExposedObject(IUnknown **pComIP); + + MethodTable *GetRedirectedType(WinMDAdapter::RedirectedTypeIndex index); +#endif // FEATURE_COMINTEROP + + + //**************************************************************************************** + +protected: + // Multi-thread safe access to the list of assemblies + class DomainAssemblyList + { + private: + ArrayList m_array; +#ifdef _DEBUG + AppDomain * dbg_m_pAppDomain; + public: + void Debug_SetAppDomain(AppDomain * pAppDomain) + { + dbg_m_pAppDomain = pAppDomain; + } +#endif //_DEBUG + public: + bool IsEmpty() + { + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } CONTRACTL_END; + + // This function can be reliably called without taking the lock, because the first assembly + // added to the arraylist is non-collectible, and the ArrayList itself allows lockless read access + return (m_array.GetCount() == 0); + } + void Clear(AppDomain * pAppDomain) + { + CONTRACTL { + NOTHROW; + WRAPPER(GC_TRIGGERS); // Triggers only in MODE_COOPERATIVE (by taking the lock) + MODE_ANY; + } CONTRACTL_END; + + _ASSERTE(dbg_m_pAppDomain == pAppDomain); + + CrstHolder ch(pAppDomain->GetAssemblyListLock()); + m_array.Clear(); + } + + DWORD GetCount(AppDomain * pAppDomain) + { + CONTRACTL { + NOTHROW; + WRAPPER(GC_TRIGGERS); // Triggers only in MODE_COOPERATIVE (by taking the lock) + MODE_ANY; + } CONTRACTL_END; + + _ASSERTE(dbg_m_pAppDomain == pAppDomain); + + CrstHolder ch(pAppDomain->GetAssemblyListLock()); + return GetCount_Unlocked(); + } + DWORD GetCount_Unlocked() + { + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } CONTRACTL_END; + +#ifndef DACCESS_COMPILE + _ASSERTE(dbg_m_pAppDomain->GetAssemblyListLock()->OwnedByCurrentThread()); +#endif + // code:Append_Unlock guarantees that we do not have more than MAXDWORD items + return m_array.GetCount(); + } + + void Get(AppDomain * pAppDomain, DWORD index, CollectibleAssemblyHolder * pAssemblyHolder) + { + CONTRACTL { + NOTHROW; + WRAPPER(GC_TRIGGERS); // Triggers only in MODE_COOPERATIVE (by taking the lock) + MODE_ANY; + } CONTRACTL_END; + + _ASSERTE(dbg_m_pAppDomain == pAppDomain); + + CrstHolder ch(pAppDomain->GetAssemblyListLock()); + Get_Unlocked(index, pAssemblyHolder); + } + void Get_Unlocked(DWORD index, CollectibleAssemblyHolder * pAssemblyHolder) + { + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } CONTRACTL_END; + + _ASSERTE(dbg_m_pAppDomain->GetAssemblyListLock()->OwnedByCurrentThread()); + *pAssemblyHolder = dac_cast(m_array.Get(index)); + } + // Doesn't lock the assembly list (caller has to hold the lock already). + // Doesn't AddRef the returned assembly (if collectible). + DomainAssembly * Get_UnlockedNoReference(DWORD index) + { + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + SUPPORTS_DAC; + } CONTRACTL_END; + +#ifndef DACCESS_COMPILE + _ASSERTE(dbg_m_pAppDomain->GetAssemblyListLock()->OwnedByCurrentThread()); +#endif + return dac_cast(m_array.Get(index)); + } + +#ifndef DACCESS_COMPILE + void Set(AppDomain * pAppDomain, DWORD index, DomainAssembly * pAssembly) + { + CONTRACTL { + NOTHROW; + WRAPPER(GC_TRIGGERS); // Triggers only in MODE_COOPERATIVE (by taking the lock) + MODE_ANY; + } CONTRACTL_END; + + _ASSERTE(dbg_m_pAppDomain == pAppDomain); + + CrstHolder ch(pAppDomain->GetAssemblyListLock()); + return Set_Unlocked(index, pAssembly); + } + void Set_Unlocked(DWORD index, DomainAssembly * pAssembly) + { + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } CONTRACTL_END; + + _ASSERTE(dbg_m_pAppDomain->GetAssemblyListLock()->OwnedByCurrentThread()); + m_array.Set(index, pAssembly); + } + + HRESULT Append_Unlocked(DomainAssembly * pAssembly) + { + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } CONTRACTL_END; + + _ASSERTE(dbg_m_pAppDomain->GetAssemblyListLock()->OwnedByCurrentThread()); + return m_array.Append(pAssembly); + } +#else //DACCESS_COMPILE + void + EnumMemoryRegions(CLRDataEnumMemoryFlags flags) + { + SUPPORTS_DAC; + + m_array.EnumMemoryRegions(flags); + } +#endif // DACCESS_COMPILE + + // Should be used only by code:AssemblyIterator::Create + ArrayList::Iterator GetArrayListIterator() + { + return m_array.Iterate(); + } + }; // class DomainAssemblyList + + // Conceptually a list of code:Assembly structures, protected by lock code:GetAssemblyListLock + DomainAssemblyList m_Assemblies; + +public: + // Note that this lock switches thread into GC_NOTRIGGER region as GC can take it too. + CrstExplicitInit * GetAssemblyListLock() + { + LIMITED_METHOD_CONTRACT; + return &m_crstAssemblyList; + } + +public: + class AssemblyIterator + { + // AppDomain context with the assembly list + AppDomain * m_pAppDomain; + ArrayList::Iterator m_Iterator; + AssemblyIterationFlags m_assemblyIterationFlags; + + public: + BOOL Next(CollectibleAssemblyHolder * pDomainAssemblyHolder); + // Note: Does not lock the assembly list, but AddRefs collectible assemblies. + BOOL Next_Unlocked(CollectibleAssemblyHolder * pDomainAssemblyHolder); +#ifndef DACCESS_COMPILE + private: + // Can be called only from AppDomain shutdown code:AppDomain::ShutdownAssemblies. + // Note: Does not lock the assembly list and does not AddRefs collectible assemblies. + BOOL Next_UnsafeNoAddRef(DomainAssembly ** ppDomainAssembly); +#endif + + private: + inline DWORD GetIndex() + { + LIMITED_METHOD_CONTRACT; + return m_Iterator.GetIndex(); + } + + private: + friend class AppDomain; + // Cannot have constructor so this iterator can be used inside a union + static AssemblyIterator Create(AppDomain * pAppDomain, AssemblyIterationFlags assemblyIterationFlags) + { + LIMITED_METHOD_CONTRACT; + AssemblyIterator i; + + i.m_pAppDomain = pAppDomain; + i.m_Iterator = pAppDomain->m_Assemblies.GetArrayListIterator(); + i.m_assemblyIterationFlags = assemblyIterationFlags; + return i; + } + }; // class AssemblyIterator + + AssemblyIterator IterateAssembliesEx(AssemblyIterationFlags assemblyIterationFlags) + { + LIMITED_METHOD_CONTRACT; + return AssemblyIterator::Create(this, assemblyIterationFlags); + } + +#ifdef FEATURE_CORECLR +private: + struct NativeImageDependenciesEntry + { + BaseAssemblySpec m_AssemblySpec; + GUID m_guidMVID; + }; + + class NativeImageDependenciesTraits : public NoRemoveSHashTraits > + { + public: + typedef BaseAssemblySpec *key_t; + static key_t GetKey(NativeImageDependenciesEntry * e) { return &(e->m_AssemblySpec); } + + static count_t Hash(key_t k) + { + return k->Hash(); + } + + static BOOL Equals(key_t lhs, key_t rhs) + { + return lhs->CompareEx(rhs); + } + }; + + SHash m_NativeImageDependencies; + +public: + void CheckForMismatchedNativeImages(AssemblySpec * pSpec, const GUID * pGuid); + +public: + class PathIterator + { + friend class AppDomain; + + ArrayList::Iterator m_i; + + public: + BOOL Next() + { + WRAPPER_NO_CONTRACT; + return m_i.Next(); + } + + SString* GetPath() + { + WRAPPER_NO_CONTRACT; + return dac_cast(m_i.GetElement()); + } + }; + BOOL BindingByManifestFile(); + + PathIterator IterateNativeDllSearchDirectories(); + void SetNativeDllSearchDirectories(LPCWSTR paths); + BOOL HasNativeDllSearchDirectories(); + void ShutdownNativeDllSearchDirectories(); +#endif // FEATURE_CORECLR + +public: + SIZE_T GetAssemblyCount() + { + WRAPPER_NO_CONTRACT; + return m_Assemblies.GetCount(this); + } + + CHECK CheckCanLoadTypes(Assembly *pAssembly); + CHECK CheckCanExecuteManagedCode(MethodDesc* pMD); + CHECK CheckLoading(DomainFile *pFile, FileLoadLevel level); + + FileLoadLevel GetDomainFileLoadLevel(DomainFile *pFile); + BOOL IsLoading(DomainFile *pFile, FileLoadLevel level); + static FileLoadLevel GetThreadFileLoadLevel(); + + void LoadDomainFile(DomainFile *pFile, + FileLoadLevel targetLevel); + + enum FindAssemblyOptions + { + FindAssemblyOptions_None = 0x0, + FindAssemblyOptions_IncludeFailedToLoad = 0x1 + }; + + DomainAssembly * FindAssembly(PEAssembly * pFile, FindAssemblyOptions options = FindAssemblyOptions_None) DAC_EMPTY_RET(NULL); + +#ifdef FEATURE_MIXEDMODE + // Finds only loaded modules, elevates level if needed + Module* GetIJWModule(HMODULE hMod) DAC_EMPTY_RET(NULL); + // Finds loading modules + DomainFile* FindIJWDomainFile(HMODULE hMod, const SString &path) DAC_EMPTY_RET(NULL); +#endif // FEATURE_MIXEDMODE + + Assembly *LoadAssembly(AssemblySpec* pIdentity, + PEAssembly *pFile, + FileLoadLevel targetLevel, + AssemblyLoadSecurity *pLoadSecurity = NULL); + + // this function does not provide caching, you must use LoadDomainAssembly + // unless the call is guaranteed to succeed or you don't need the caching + // (e.g. if you will FailFast or tear down the AppDomain anyway) + // The main point that you should not bypass caching if you might try to load the same file again, + // resulting in multiple DomainAssembly objects that share the same PEAssembly for ngen image + //which is violating our internal assumptions + DomainAssembly *LoadDomainAssemblyInternal( AssemblySpec* pIdentity, + PEAssembly *pFile, + FileLoadLevel targetLevel, + AssemblyLoadSecurity *pLoadSecurity = NULL); + + DomainAssembly *LoadDomainAssembly( AssemblySpec* pIdentity, + PEAssembly *pFile, + FileLoadLevel targetLevel, + AssemblyLoadSecurity *pLoadSecurity = NULL); + +#ifdef FEATURE_MULTIMODULE_ASSEMBLIES + DomainModule *LoadDomainModule(DomainAssembly *pAssembly, + PEModule *pFile, + FileLoadLevel targetLevel); +#endif + + CHECK CheckValidModule(Module *pModule); +#ifdef FEATURE_LOADER_OPTIMIZATION + DomainFile *LoadDomainNeutralModuleDependency(Module *pModule, FileLoadLevel targetLevel); +#endif + +#ifdef FEATURE_FUSION + PEAssembly *BindExplicitAssembly(HMODULE hMod, BOOL bindable); + Assembly *LoadExplicitAssembly(HMODULE hMod, BOOL bindable); + void GetFileFromFusion(IAssembly *pIAssembly, LPCWSTR wszModuleName, + SString &path); +#endif + // private: + void LoadSystemAssemblies(); + + DomainFile *LoadDomainFile(FileLoadLock *pLock, + FileLoadLevel targetLevel); + + void TryIncrementalLoad(DomainFile *pFile, FileLoadLevel workLevel, FileLoadLockHolder &lockHolder); + + Assembly *LoadAssemblyHelper(LPCWSTR wszAssembly, + LPCWSTR wszCodeBase); + +#ifndef DACCESS_COMPILE // needs AssemblySpec + //**************************************************************************************** + // Returns and Inserts assemblies into a lookup cache based on the binding information + // in the AssemblySpec. There can be many AssemblySpecs to a single assembly. + DomainAssembly* FindCachedAssembly(AssemblySpec* pSpec, BOOL fThrow=TRUE) + { + WRAPPER_NO_CONTRACT; + return m_AssemblyCache.LookupAssembly(pSpec, fThrow); + } + + PEAssembly* FindCachedFile(AssemblySpec* pSpec, BOOL fThrow = TRUE); + BOOL IsCached(AssemblySpec *pSpec); +#endif // DACCESS_COMPILE + void CacheStringsForDAC(); + + BOOL AddFileToCache(AssemblySpec* pSpec, PEAssembly *pFile, BOOL fAllowFailure = FALSE); + BOOL AddAssemblyToCache(AssemblySpec* pSpec, DomainAssembly *pAssembly); + BOOL AddExceptionToCache(AssemblySpec* pSpec, Exception *ex); + void AddUnmanagedImageToCache(LPCWSTR libraryName, HMODULE hMod); + HMODULE FindUnmanagedImageInCache(LPCWSTR libraryName); + //**************************************************************************************** + // + // Adds an assembly to the domain. + void AddAssembly(DomainAssembly * assem); + void RemoveAssembly_Unlocked(DomainAssembly * pAsm); + + BOOL ContainsAssembly(Assembly * assem); + +#ifdef FEATURE_LOADER_OPTIMIZATION + enum SharePolicy + { + // Attributes to control when to use domain neutral assemblies + SHARE_POLICY_UNSPECIFIED, // Use the current default policy (LoaderOptimization.NotSpecified) + SHARE_POLICY_NEVER, // Do not share anything, except the system assembly (LoaderOptimization.SingleDomain) + SHARE_POLICY_ALWAYS, // Share everything possible (LoaderOptimization.MultiDomain) + SHARE_POLICY_GAC, // Share only GAC-bound assemblies (LoaderOptimization.MultiDomainHost) + + SHARE_POLICY_COUNT, + SHARE_POLICY_MASK = 0x3, + + // NOTE that previously defined was a bit 0x40 which might be set on this value + // in custom attributes. + SHARE_POLICY_DEFAULT = SHARE_POLICY_NEVER, + }; + + void SetSharePolicy(SharePolicy policy); + SharePolicy GetSharePolicy(); + BOOL ReduceSharePolicyFromAlways(); + + //**************************************************************************************** + // Determines if the image is to be loaded into the shared assembly or an individual + // appdomains. +#ifndef FEATURE_CORECLR + BOOL ApplySharePolicy(DomainAssembly *pFile); + BOOL ApplySharePolicyFlag(DomainAssembly *pFile); +#endif +#endif // FEATURE_LOADER_OPTIMIZATION + + BOOL HasSetSecurityPolicy(); + + FORCEINLINE IApplicationSecurityDescriptor* GetSecurityDescriptor() + { + LIMITED_METHOD_CONTRACT; + STATIC_CONTRACT_SO_TOLERANT; + return static_cast(m_pSecDesc); + } + + void CreateSecurityDescriptor(); + + //**************************************************************************************** + // + // Reference count. When an appdomain is first created the reference is bump + // to one when it is added to the list of domains (see SystemDomain). An explicit + // Removal from the list is necessary before it will be deleted. + ULONG AddRef(void); + ULONG Release(void) DAC_EMPTY_RET(0); + + //**************************************************************************************** + LPCWSTR GetFriendlyName(BOOL fDebuggerCares = TRUE); + LPCWSTR GetFriendlyNameForDebugger(); + LPCWSTR GetFriendlyNameForLogging(); +#ifdef DACCESS_COMPILE + PVOID GetFriendlyNameNoSet(bool* isUtf8); +#endif + void SetFriendlyName(LPCWSTR pwzFriendlyName, BOOL fDebuggerCares = TRUE); + void ResetFriendlyName(BOOL fDebuggerCares = TRUE); + + //**************************************************************************************** + + // This can be used to override the binding behavior of the appdomain. It + // is overridden in the compilation domain. It is important that all + // static binding goes through this path. + virtual PEAssembly * BindAssemblySpec( + AssemblySpec *pSpec, + BOOL fThrowOnFileNotFound, + BOOL fRaisePrebindEvents, + StackCrawlMark *pCallerStackMark = NULL, + AssemblyLoadSecurity *pLoadSecurity = NULL, + BOOL fUseHostBinderIfAvailable = TRUE) DAC_EMPTY_RET(NULL); + + HRESULT BindAssemblySpecForHostedBinder( + AssemblySpec * pSpec, + IAssemblyName * pAssemblyName, + ICLRPrivBinder * pBinder, + PEAssembly ** ppAssembly) DAC_EMPTY_RET(E_FAIL); + + HRESULT BindHostedPrivAssembly( + PEAssembly * pParentPEAssembly, + ICLRPrivAssembly * pPrivAssembly, + IAssemblyName * pAssemblyName, + PEAssembly ** ppAssembly, + BOOL fIsIntrospectionOnly = FALSE) DAC_EMPTY_RET(S_OK); + +#ifdef FEATURE_REFLECTION_ONLY_LOAD + virtual DomainAssembly *BindAssemblySpecForIntrospectionDependencies(AssemblySpec *pSpec) DAC_EMPTY_RET(NULL); +#endif + + PEAssembly *TryResolveAssembly(AssemblySpec *pSpec, BOOL fPreBind); + + // Store a successful binding into the cache. This will keep the file from + // being physically unmapped, as well as shortcutting future attempts to bind + // the same spec throught the Cached entry point. + // + // Right now we only cache assembly binds for "probing" type + // binding situations, basically when loading domain neutral assemblies or + // zap files. + // + // @todo: We may want to be more aggressive about this if + // there are other situations where we are repeatedly binding the + // same assembly specs, though. + // + // Returns TRUE if stored + // FALSE if it's a duplicate (caller should clean up args) + BOOL StoreBindAssemblySpecResult(AssemblySpec *pSpec, + PEAssembly *pFile, + BOOL clone = TRUE); + + BOOL StoreBindAssemblySpecError(AssemblySpec *pSpec, + HRESULT hr, + OBJECTREF *pThrowable, + BOOL clone = TRUE); + + //**************************************************************************************** + // +#ifdef FEATURE_FUSION + static BOOL SetContextProperty(IApplicationContext* pFusionContext, + LPCWSTR pProperty, + OBJECTREF* obj); +#endif + //**************************************************************************************** + // + // Uses the first assembly to add an application base to the Context. This is done + // in a lazy fashion so executables do not take the perf hit unless the load other + // assemblies +#ifdef FEATURE_FUSION + LPWSTR GetDynamicDir(); +#endif +#ifndef DACCESS_COMPILE + void OnAssemblyLoad(Assembly *assem); + void OnAssemblyLoadUnlocked(Assembly *assem); + static BOOL OnUnhandledException(OBJECTREF *pThrowable, BOOL isTerminating = TRUE); + +#endif + + // True iff a debugger is attached to the process (same as CORDebuggerAttached) + BOOL IsDebuggerAttached (void); + +#ifdef DEBUGGING_SUPPORTED + // Notify debugger of all assemblies, modules, and possibly classes in this AppDomain + BOOL NotifyDebuggerLoad(int flags, BOOL attaching); + + // Send unload notifications to the debugger for all assemblies, modules and classes in this AppDomain + void NotifyDebuggerUnload(); +#endif // DEBUGGING_SUPPORTED + + void SetSystemAssemblyLoadEventSent (BOOL fFlag); + BOOL WasSystemAssemblyLoadEventSent (void); + +#ifndef DACCESS_COMPILE + OBJECTREF* AllocateStaticFieldObjRefPtrs(int nRequested, OBJECTREF** ppLazyAllocate = NULL) + { + WRAPPER_NO_CONTRACT; + + return AllocateObjRefPtrsInLargeTable(nRequested, ppLazyAllocate); + } + + OBJECTREF* AllocateStaticFieldObjRefPtrsCrossDomain(int nRequested, OBJECTREF** ppLazyAllocate = NULL) + { + WRAPPER_NO_CONTRACT; + + return AllocateObjRefPtrsInLargeTable(nRequested, ppLazyAllocate, TRUE); + } +#endif // DACCESS_COMPILE + + void EnumStaticGCRefs(promote_func* fn, ScanContext* sc); + + DomainLocalBlock *GetDomainLocalBlock() + { + LIMITED_METHOD_DAC_CONTRACT; + + return &m_sDomainLocalBlock; + } + + static SIZE_T GetOffsetOfModuleSlotsPointer() + { + WRAPPER_NO_CONTRACT; + + return offsetof(AppDomain,m_sDomainLocalBlock) + DomainLocalBlock::GetOffsetOfModuleSlotsPointer(); + } + + void SetupSharedStatics(); + + ADUnloadSink* PrepareForWaitUnloadCompletion(); + + //**************************************************************************************** + // + // Create a quick lookup for classes loaded into this domain based on their GUID. + // + void InsertClassForCLSID(MethodTable* pMT, BOOL fForceInsert = FALSE); + void InsertClassForCLSID(MethodTable* pMT, GUID *pGuid); + +#ifdef FEATURE_COMINTEROP +private: + void CacheTypeByNameWorker(const SString &ssClassName, const UINT vCacheVersion, TypeHandle typeHandle, BYTE flags, BOOL bReplaceExisting = FALSE); + TypeHandle LookupTypeByNameWorker(const SString &ssClassName, UINT *pvCacheVersion, BYTE *pbFlags); +public: + // Used by COM Interop for mapping WinRT runtime class names to real types. + void CacheTypeByName(const SString &ssClassName, const UINT vCacheVersion, TypeHandle typeHandle, BYTE flags, BOOL bReplaceExisting = FALSE); + TypeHandle LookupTypeByName(const SString &ssClassName, UINT *pvCacheVersion, BYTE *pbFlags); + PTR_MethodTable LookupTypeByGuid(const GUID & guid); + +#ifndef DACCESS_COMPILE + inline BOOL CanCacheWinRTTypeByGuid(TypeHandle typeHandle) + { + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // Only allow caching guid/types maps for types loaded during + // "normal" domain operation + if (IsCompilationDomain() || (m_Stage < STAGE_OPEN)) + return FALSE; + + MethodTable *pMT = typeHandle.GetMethodTable(); + if (pMT != NULL) + { + // Don't cache mscorlib-internal declarations of WinRT types. + if (pMT->GetModule()->IsSystem() && pMT->IsProjectedFromWinRT()) + return FALSE; + + // Don't cache redirected WinRT types. + if (WinRTTypeNameConverter::IsRedirectedWinRTSourceType(pMT)) + return FALSE; + } + + return TRUE; + } +#endif // !DACCESS_COMPILE + + void CacheWinRTTypeByGuid(TypeHandle typeHandle); + void GetCachedWinRTTypes(SArray * pTypes, SArray * pGuids, UINT minEpoch, UINT * pCurEpoch); + + // Used by COM Interop for caching WinRT factory objects. + void CacheWinRTFactoryObject(MethodTable *pClassMT, OBJECTREF *refFactory, LPVOID lpCtxCookie); + OBJECTREF LookupWinRTFactoryObject(MethodTable *pClassMT, LPVOID lpCtxCookie); + void RemoveWinRTFactoryObjects(LPVOID pCtxCookie); + + MethodTable *LoadCOMClass(GUID clsid, BOOL bLoadRecord = FALSE, BOOL* pfAssemblyInReg = NULL); + COMorRemotingFlag GetComOrRemotingFlag(); + BOOL GetPreferComInsteadOfManagedRemoting(); + OBJECTREF GetMissingObject(); // DispatchInfo will call function to retrieve the Missing.Value object. +#endif // FEATURE_COMINTEROP + +#ifndef DACCESS_COMPILE + MethodTable* LookupClass(REFIID iid) + { + WRAPPER_NO_CONTRACT; + + MethodTable *pMT = (MethodTable*) m_clsidHash.LookupValue((UPTR) GetKeyFromGUID(&iid), (LPVOID)&iid); + return (pMT == (MethodTable*) INVALIDENTRY + ? NULL + : pMT); + } +#endif // DACCESS_COMPILE + + //@todo get a better key + ULONG GetKeyFromGUID(const GUID *pguid) + { + LIMITED_METHOD_CONTRACT; + + return *(ULONG *) pguid; + } + +#ifdef FEATURE_COMINTEROP + ComCallWrapperCache* GetComCallWrapperCache(); + RCWCache *GetRCWCache() + { + WRAPPER_NO_CONTRACT; + if (m_pRCWCache) + return m_pRCWCache; + + // By separating the cache creation from the common lookup, we + // can keep the (x86) EH prolog/epilog off the path. + return CreateRCWCache(); + } +private: + RCWCache *CreateRCWCache(); +public: + RCWCache *GetRCWCacheNoCreate() + { + LIMITED_METHOD_CONTRACT; + return m_pRCWCache; + } + + RCWRefCache *GetRCWRefCache(); + + void ResetComCallWrapperCache() + { + LIMITED_METHOD_CONTRACT; + m_pComCallWrapperCache = NULL; + } + + MethodTable* GetLicenseInteropHelperMethodTable(); +#endif // FEATURE_COMINTEROP + + //**************************************************************************************** + // Get the proxy for this app domain +#ifdef FEATURE_REMOTING + OBJECTREF GetAppDomainProxy(); +#endif + + ADIndex GetIndex() + { + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + + return m_dwIndex; + } + + TPIndex GetTPIndex() + { + LIMITED_METHOD_CONTRACT; + return m_tpIndex; + } + + void InitializeDomainContext(BOOL allowRedirects, LPCWSTR pwszPath, LPCWSTR pwszConfig); + +#ifdef FEATURE_FUSION + IApplicationContext *CreateFusionContext(); + void SetupLoaderOptimization(DWORD optimization); +#endif +#ifdef FEATURE_VERSIONING + IUnknown *CreateFusionContext(); +#endif // FEATURE_VERSIONING + +#if defined(FEATURE_HOST_ASSEMBLY_RESOLVER) + void OverrideDefaultContextBinder(IUnknown *pOverrideBinder) + { + LIMITED_METHOD_CONTRACT; + + _ASSERTE(pOverrideBinder != NULL); + pOverrideBinder->AddRef(); + m_pFusionContext->Release(); + m_pFusionContext = pOverrideBinder; + } + +#endif // defined(FEATURE_HOST_ASSEMBLY_RESOLVER) + +#ifdef FEATURE_PREJIT + CorCompileConfigFlags GetNativeConfigFlags(); +#endif // FEATURE_PREJIT + + //**************************************************************************************** + // Create a domain context rooted at the fileName. The directory containing the file name + // is the application base and the configuration file is the fileName appended with + // .config. If no name is passed in then no domain is created. + static AppDomain* CreateDomainContext(LPCWSTR fileName); + + // Sets up the current domain's fusion context based on the given exe file name + // (app base & config file) + void SetupExecutableFusionContext(LPCWSTR exePath); + + //**************************************************************************************** + // Manage a pool of asyncrhonous objects used to fetch assemblies. When a sink is released + // it places itself back on the pool list. Only one object is kept in the pool. +#ifdef FEATURE_FUSION + AssemblySink* AllocateAssemblySink(AssemblySpec* pSpec); +#endif + void SetIsUserCreatedDomain() + { + LIMITED_METHOD_CONTRACT; + + m_dwFlags |= USER_CREATED_DOMAIN; + } + + BOOL IsUserCreatedDomain() + { + LIMITED_METHOD_CONTRACT; + + return (m_dwFlags & USER_CREATED_DOMAIN); + } + + void SetIgnoreUnhandledExceptions() + { + LIMITED_METHOD_CONTRACT; + + m_dwFlags |= IGNORE_UNHANDLED_EXCEPTIONS; + } + + BOOL IgnoreUnhandledExceptions() + { + LIMITED_METHOD_CONTRACT; + + return (m_dwFlags & IGNORE_UNHANDLED_EXCEPTIONS); + } + + void SetPassiveDomain() + { + LIMITED_METHOD_CONTRACT; + + m_dwFlags |= PASSIVE_DOMAIN; + } + + BOOL IsPassiveDomain() + { + LIMITED_METHOD_CONTRACT; + + return (m_dwFlags & PASSIVE_DOMAIN); + } + + void SetVerificationDomain() + { + LIMITED_METHOD_CONTRACT; + + m_dwFlags |= VERIFICATION_DOMAIN; + } + + BOOL IsVerificationDomain() + { + LIMITED_METHOD_CONTRACT; + + return (m_dwFlags & VERIFICATION_DOMAIN); + } + + void SetIllegalVerificationDomain() + { + LIMITED_METHOD_CONTRACT; + + m_dwFlags |= ILLEGAL_VERIFICATION_DOMAIN; + } + + BOOL IsIllegalVerificationDomain() + { + LIMITED_METHOD_CONTRACT; + + return (m_dwFlags & ILLEGAL_VERIFICATION_DOMAIN); + } + + void SetCompilationDomain() + { + LIMITED_METHOD_CONTRACT; + + m_dwFlags |= (PASSIVE_DOMAIN|COMPILATION_DOMAIN); + } + + BOOL IsCompilationDomain(); + + PTR_CompilationDomain ToCompilationDomain() + { + LIMITED_METHOD_CONTRACT; + + _ASSERTE(IsCompilationDomain()); + return dac_cast(this); + } + + void SetCanUnload() + { + LIMITED_METHOD_CONTRACT; + + m_dwFlags |= APP_DOMAIN_CAN_BE_UNLOADED; + } + + BOOL CanUnload() + { + LIMITED_METHOD_CONTRACT; + STATIC_CONTRACT_SO_TOLERANT; + return m_dwFlags & APP_DOMAIN_CAN_BE_UNLOADED; + } + + void SetRemotingConfigured() + { + LIMITED_METHOD_CONTRACT; + STATIC_CONTRACT_SO_TOLERANT; + FastInterlockOr((ULONG*)&m_dwFlags, REMOTING_CONFIGURED_FOR_DOMAIN); + } + + BOOL IsRemotingConfigured() + { + LIMITED_METHOD_CONTRACT; + STATIC_CONTRACT_SO_TOLERANT; + return m_dwFlags & REMOTING_CONFIGURED_FOR_DOMAIN; + } + + void SetOrphanedLocks() + { + LIMITED_METHOD_CONTRACT; + STATIC_CONTRACT_SO_TOLERANT; + FastInterlockOr((ULONG*)&m_dwFlags, ORPHANED_LOCKS); + } + + BOOL HasOrphanedLocks() + { + LIMITED_METHOD_CONTRACT; + STATIC_CONTRACT_SO_TOLERANT; + return m_dwFlags & ORPHANED_LOCKS; + } + + // This function is used to relax asserts in the lock accounting. + // It returns true if we are fine with hosed lock accounting in this domain. + BOOL OkToIgnoreOrphanedLocks() + { + WRAPPER_NO_CONTRACT; + return HasOrphanedLocks() && m_Stage >= STAGE_UNLOAD_REQUESTED; + } + + static void ExceptionUnwind(Frame *pFrame); + +#ifdef _DEBUG + void TrackADThreadEnter(Thread *pThread, Frame *pFrame); + void TrackADThreadExit(Thread *pThread, Frame *pFrame); + void DumpADThreadTrack(); +#endif + +#ifndef DACCESS_COMPILE + void ThreadEnter(Thread *pThread, Frame *pFrame) + { + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + +#ifdef _DEBUG + if (LoggingOn(LF_APPDOMAIN, LL_INFO100)) + TrackADThreadEnter(pThread, pFrame); + else +#endif + { + InterlockedIncrement((LONG*)&m_dwThreadEnterCount); + LOG((LF_APPDOMAIN, LL_INFO1000, "AppDomain::ThreadEnter %p to [%d] (%8.8x) %S count %d\n", + pThread,GetId().m_dwId, this, + GetFriendlyNameForLogging(),GetThreadEnterCount())); +#if _DEBUG_AD_UNLOAD + printf("AppDomain::ThreadEnter %p to [%d] (%8.8x) %S count %d\n", + pThread, GetId().m_dwId, this, + GetFriendlyNameForLogging(), GetThreadEnterCount()); +#endif + } + } + + void ThreadExit(Thread *pThread, Frame *pFrame) + { + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + +#ifdef _DEBUG + if (LoggingOn(LF_APPDOMAIN, LL_INFO100)) { + TrackADThreadExit(pThread, pFrame); + } + else +#endif + { + LONG result; + result = InterlockedDecrement((LONG*)&m_dwThreadEnterCount); + _ASSERTE(result >= 0); + LOG((LF_APPDOMAIN, LL_INFO1000, "AppDomain::ThreadExit from [%d] (%8.8x) %S count %d\n", + this, GetId().m_dwId, + GetFriendlyNameForLogging(), GetThreadEnterCount())); +#if _DEBUG_ADUNLOAD + printf("AppDomain::ThreadExit %x from [%d] (%8.8x) %S count %d\n", + pThread->GetThreadId(), this, GetId().m_dwId, + GetFriendlyNameForLogging(), GetThreadEnterCount()); +#endif + } + } +#endif // DACCESS_COMPILE + + ULONG GetThreadEnterCount() + { + LIMITED_METHOD_CONTRACT; + return m_dwThreadEnterCount; + } + + BOOL OnlyOneThreadLeft() + { + LIMITED_METHOD_CONTRACT; + return m_dwThreadEnterCount==1 || m_dwThreadsStillInAppDomain ==1; + } + + Context *GetDefaultContext() + { + LIMITED_METHOD_CONTRACT; + return m_pDefaultContext; + } + + BOOL CanLoadCode() + { + LIMITED_METHOD_CONTRACT; + return m_Stage >= STAGE_READYFORMANAGEDCODE && m_Stage < STAGE_CLOSED; + } + + void SetAnonymouslyHostedDynamicMethodsAssembly(DomainAssembly * pDomainAssembly) + { + LIMITED_METHOD_CONTRACT; + _ASSERTE(pDomainAssembly != NULL); + _ASSERTE(m_anonymouslyHostedDynamicMethodsAssembly == NULL); + m_anonymouslyHostedDynamicMethodsAssembly = pDomainAssembly; + } + + DomainAssembly * GetAnonymouslyHostedDynamicMethodsAssembly() + { + LIMITED_METHOD_CONTRACT; + return m_anonymouslyHostedDynamicMethodsAssembly; + } + + BOOL HasUnloadStarted() + { + LIMITED_METHOD_CONTRACT; + return m_Stage>=STAGE_EXITED; + } + static void RefTakerAcquire(AppDomain* pDomain) + { + WRAPPER_NO_CONTRACT; + if(!pDomain) + return; + pDomain->AddRef(); +#ifdef _DEBUG + FastInterlockIncrement(&pDomain->m_dwRefTakers); +#endif + } + + static void RefTakerRelease(AppDomain* pDomain) + { + WRAPPER_NO_CONTRACT; + if(!pDomain) + return; +#ifdef _DEBUG + _ASSERTE(pDomain->m_dwRefTakers); + FastInterlockDecrement(&pDomain->m_dwRefTakers); +#endif + pDomain->Release(); + } + +#ifdef _DEBUG + + BOOL IsHeldByIterator() + { + LIMITED_METHOD_CONTRACT; + return m_dwIterHolders>0; + } + + BOOL IsHeldByRefTaker() + { + LIMITED_METHOD_CONTRACT; + return m_dwRefTakers>0; + } + + void IteratorRelease() + { + LIMITED_METHOD_CONTRACT; + _ASSERTE(m_dwIterHolders); + FastInterlockDecrement(&m_dwIterHolders); + } + + + void IteratorAcquire() + { + LIMITED_METHOD_CONTRACT; + FastInterlockIncrement(&m_dwIterHolders); + } + +#endif + BOOL IsActive() + { + LIMITED_METHOD_DAC_CONTRACT; + + return m_Stage >= STAGE_ACTIVE && m_Stage < STAGE_CLOSED; + } + // Range for normal execution of code in the appdomain, currently used for + // appdomain resource monitoring since we don't care to update resource usage + // unless it's in these stages (as fields of AppDomain may not be valid if it's + // not within these stages) + BOOL IsUserActive() + { + LIMITED_METHOD_DAC_CONTRACT; + + return m_Stage >= STAGE_ACTIVE && m_Stage <= STAGE_OPEN; + } + BOOL IsValid() + { + LIMITED_METHOD_DAC_CONTRACT; + +#ifdef DACCESS_COMPILE + // We want to see all appdomains in SOS, even the about to be destructed ones. + // There is no risk of races under DAC, so we will pretend to be unconditionally valid. + return TRUE; +#else + return m_Stage > STAGE_CREATING && m_Stage < STAGE_CLOSED; +#endif + } + +#ifdef _DEBUG + BOOL IsBeingCreated() + { + LIMITED_METHOD_CONTRACT; + + return m_dwCreationHolders > 0; + } + + void IncCreationCount() + { + LIMITED_METHOD_CONTRACT; + + FastInterlockIncrement(&m_dwCreationHolders); + _ASSERTE(m_dwCreationHolders > 0); + } + + void DecCreationCount() + { + LIMITED_METHOD_CONTRACT; + + FastInterlockDecrement(&m_dwCreationHolders); + _ASSERTE(m_dwCreationHolders > -1); + } +#endif + BOOL IsRunningIn(Thread* pThread); + + BOOL IsUnloading() + { + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + + return m_Stage > STAGE_UNLOAD_REQUESTED; + } + + BOOL NotReadyForManagedCode() + { + LIMITED_METHOD_CONTRACT; + + return m_Stage < STAGE_READYFORMANAGEDCODE; + } + + void SetFinalized() + { + LIMITED_METHOD_CONTRACT; + SetStage(STAGE_FINALIZED); + } + + BOOL IsFinalizing() + { + LIMITED_METHOD_CONTRACT; + + return m_Stage >= STAGE_FINALIZING; + } + + BOOL IsFinalized() + { + LIMITED_METHOD_CONTRACT; + + return m_Stage >= STAGE_FINALIZED; + } + + BOOL NoAccessToHandleTable() + { + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + + return m_Stage >= STAGE_HANDLETABLE_NOACCESS; + } + + // Checks whether the given thread can enter the app domain + BOOL CanThreadEnter(Thread *pThread); + + // Following two are needed for the Holder + static void SetUnloadInProgress(AppDomain *pThis) PUB; + static void SetUnloadComplete(AppDomain *pThis) PUB; + // Predicates for GC asserts + BOOL ShouldHaveFinalization() + { + LIMITED_METHOD_CONTRACT; + + return ((DWORD) m_Stage) < STAGE_COLLECTED; + } + BOOL ShouldHaveCode() + { + LIMITED_METHOD_CONTRACT; + + return ((DWORD) m_Stage) < STAGE_COLLECTED; + } + BOOL ShouldHaveRoots() + { + LIMITED_METHOD_CONTRACT; + + return ((DWORD) m_Stage) < STAGE_CLEARED; + } + BOOL ShouldHaveInstances() + { + LIMITED_METHOD_CONTRACT; + + return ((DWORD) m_Stage) < STAGE_COLLECTED; + } + + + static void RaiseExitProcessEvent(); + Assembly* RaiseResourceResolveEvent(DomainAssembly* pAssembly, LPCSTR szName); + DomainAssembly* RaiseTypeResolveEventThrowing(DomainAssembly* pAssembly, LPCSTR szName, ASSEMBLYREF *pResultingAssemblyRef); + Assembly* RaiseAssemblyResolveEvent(AssemblySpec *pSpec, BOOL fIntrospection, BOOL fPreBind); + +private: + CrstExplicitInit m_ReflectionCrst; + CrstExplicitInit m_RefClassFactCrst; + + + EEClassFactoryInfoHashTable *m_pRefClassFactHash; // Hash table that maps a class factory info to a COM comp. +#ifdef FEATURE_COMINTEROP + DispIDCache *m_pRefDispIDCache; + COMorRemotingFlag m_COMorRemotingFlag; + OBJECTHANDLE m_hndMissing; //Handle points to Missing.Value Object which is used for [Optional] arg scenario during IDispatch CCW Call + + MethodTable* m_rpCLRTypes[WinMDAdapter::RedirectedTypeIndex_Count]; + + MethodTable* LoadRedirectedType(WinMDAdapter::RedirectedTypeIndex index, WinMDAdapter::FrameworkAssemblyIndex assembly); +#endif // FEATURE_COMINTEROP + +public: + + CrstBase *GetRefClassFactCrst() + { + LIMITED_METHOD_CONTRACT; + + return &m_RefClassFactCrst; + } + +#ifndef DACCESS_COMPILE + EEClassFactoryInfoHashTable* GetClassFactHash() + { + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_FAULT; + + if (m_pRefClassFactHash != NULL) { + return m_pRefClassFactHash; + } + + return SetupClassFactHash(); + } +#endif // DACCESS_COMPILE + +#ifdef FEATURE_COMINTEROP + DispIDCache* GetRefDispIDCache() + { + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_FAULT; + + if (m_pRefDispIDCache != NULL) { + return m_pRefDispIDCache; + } + + return SetupRefDispIDCache(); + } +#endif // FEATURE_COMINTEROP + + PTR_LoaderHeap GetStubHeap(); + PTR_LoaderHeap GetLowFrequencyHeap(); + PTR_LoaderHeap GetHighFrequencyHeap(); + virtual PTR_LoaderAllocator GetLoaderAllocator(); + +#ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING + #define ARM_ETW_ALLOC_THRESHOLD (4 * 1024 * 1024) + // cache line size in ULONGLONG - 128 bytes which are 16 ULONGLONG's + #define ARM_CACHE_LINE_SIZE_ULL 16 + + inline ULONGLONG GetAllocBytes() + { + LIMITED_METHOD_CONTRACT; + ULONGLONG ullTotalAllocBytes = 0; + + // Ensure that m_pullAllocBytes is non-null to avoid an AV in a race between GC and AD unload. + // A race can occur when a new appdomain is created, but an OOM is thrown when allocating for m_pullAllocBytes, causing the AD unload. + if(NULL != m_pullAllocBytes) + { + for (DWORD i = 0; i < m_dwNumHeaps; i++) + { + ullTotalAllocBytes += m_pullAllocBytes[i * ARM_CACHE_LINE_SIZE_ULL]; + } + } + return ullTotalAllocBytes; + } + + void RecordAllocBytes(size_t allocatedBytes, DWORD dwHeapNumber) + { + LIMITED_METHOD_CONTRACT; + _ASSERTE(dwHeapNumber < m_dwNumHeaps); + + // Ensure that m_pullAllocBytes is non-null to avoid an AV in a race between GC and AD unload. + // A race can occur when a new appdomain is created, but an OOM is thrown when allocating for m_pullAllocBytes, causing the AD unload. + if(NULL != m_pullAllocBytes) + { + m_pullAllocBytes[dwHeapNumber * ARM_CACHE_LINE_SIZE_ULL] += allocatedBytes; + } + + ULONGLONG ullTotalAllocBytes = GetAllocBytes(); + + if ((ullTotalAllocBytes - m_ullLastEtwAllocBytes) >= ARM_ETW_ALLOC_THRESHOLD) + { + m_ullLastEtwAllocBytes = ullTotalAllocBytes; + FireEtwAppDomainMemAllocated((ULONGLONG)this, ullTotalAllocBytes, GetClrInstanceId()); + } + } + + inline ULONGLONG GetSurvivedBytes() + { + LIMITED_METHOD_CONTRACT; + ULONGLONG ullTotalSurvivedBytes = 0; + + // Ensure that m_pullSurvivedBytes is non-null to avoid an AV in a race between GC and AD unload. + // A race can occur when a new appdomain is created, but an OOM is thrown when allocating for m_pullSurvivedBytes, causing the AD unload. + if(NULL != m_pullSurvivedBytes) + { + for (DWORD i = 0; i < m_dwNumHeaps; i++) + { + ullTotalSurvivedBytes += m_pullSurvivedBytes[i * ARM_CACHE_LINE_SIZE_ULL]; + } + } + return ullTotalSurvivedBytes; + } + + void RecordSurvivedBytes(size_t promotedBytes, DWORD dwHeapNumber) + { + WRAPPER_NO_CONTRACT; + _ASSERTE(dwHeapNumber < m_dwNumHeaps); + + // Ensure that m_pullSurvivedBytes is non-null to avoid an AV in a race between GC and AD unload. + // A race can occur when a new appdomain is created, but an OOM is thrown when allocating for m_pullSurvivedBytes, causing the AD unload. + if(NULL != m_pullSurvivedBytes) + { + m_pullSurvivedBytes[dwHeapNumber * ARM_CACHE_LINE_SIZE_ULL] += promotedBytes; + } + } + + inline void ResetSurvivedBytes() + { + LIMITED_METHOD_CONTRACT; + + // Ensure that m_pullSurvivedBytes is non-null to avoid an AV in a race between GC and AD unload. + // A race can occur when a new appdomain is created, but an OOM is thrown when allocating for m_pullSurvivedBytes, causing the AD unload. + if(NULL != m_pullSurvivedBytes) + { + for (DWORD i = 0; i < m_dwNumHeaps; i++) + { + m_pullSurvivedBytes[i * ARM_CACHE_LINE_SIZE_ULL] = 0; + } + } + } + + // Return the total processor time (user and kernel) used by threads executing in this AppDomain so far. + // The result is in 100ns units. + ULONGLONG QueryProcessorUsage(); + + // Add to the current count of processor time used by threads within this AppDomain. This API is called by + // threads transitioning between AppDomains. + void UpdateProcessorUsage(ULONGLONG ullAdditionalUsage); +#endif //FEATURE_APPDOMAIN_RESOURCE_MONITORING + +private: + static void RaiseOneExitProcessEvent_Wrapper(AppDomainIterator* pi); + static void RaiseOneExitProcessEvent(); + size_t EstimateSize(); + EEClassFactoryInfoHashTable* SetupClassFactHash(); +#ifdef FEATURE_COMINTEROP + DispIDCache* SetupRefDispIDCache(); + COMorRemotingFlag GetPreferComInsteadOfManagedRemotingFromConfigFile(); +#endif // FEATURE_COMINTEROP + + void InitializeDefaultDomainManager (); + +#ifdef FEATURE_CLICKONCE + void InitializeDefaultClickOnceDomain(); +#endif // FEATURE_CLICKONCE + + void InitializeDefaultDomainSecurity(); +public: +#ifdef FEATURE_CLICKONCE + BOOL IsClickOnceAppDomain(); +#endif // FEATURE_CLICKONCE + +protected: + BOOL PostBindResolveAssembly(AssemblySpec *pPrePolicySpec, + AssemblySpec *pPostPolicySpec, + HRESULT hrBindResult, + AssemblySpec **ppFailedSpec); + +#ifdef FEATURE_COMINTEROP +public: + void ReleaseRCWs(LPVOID pCtxCookie); + void DetachRCWs(); + +protected: +#endif // FEATURE_COMINTEROP + + LPWSTR m_pwDynamicDir; + +private: + void RaiseLoadingAssemblyEvent(DomainAssembly* pAssembly); + + friend class DomainAssembly; + +public: + static void ProcessUnloadDomainEventOnFinalizeThread(); + static BOOL HasWorkForFinalizerThread() + { + LIMITED_METHOD_CONTRACT; + return s_pAppDomainToRaiseUnloadEvent != NULL; + } + +private: + static AppDomain* s_pAppDomainToRaiseUnloadEvent; + static BOOL s_fProcessUnloadDomainEvent; + + void RaiseUnloadDomainEvent(); + static void RaiseUnloadDomainEvent_Wrapper(LPVOID /* AppDomain * */); + + BOOL RaiseUnhandledExceptionEvent(OBJECTREF *pSender, OBJECTREF *pThrowable, BOOL isTerminating); + BOOL HasUnhandledExceptionEventHandler(); + BOOL RaiseUnhandledExceptionEventNoThrow(OBJECTREF *pSender, OBJECTREF *pThrowable, BOOL isTerminating); + + struct RaiseUnhandled_Args + { + AppDomain *pExceptionDomain; + AppDomain *pTargetDomain; + OBJECTREF *pSender; + OBJECTREF *pThrowable; + BOOL isTerminating; + BOOL *pResult; + }; + #ifndef FEATURE_CORECLR + static void RaiseUnhandledExceptionEvent_Wrapper(LPVOID /* RaiseUnhandled_Args * */); + #endif + + + static void AllowThreadEntrance(AppDomain *pApp); + static void RestrictThreadEntrance(AppDomain *pApp); + + typedef Holder,AppDomain::AllowThreadEntrance,NULL> RestrictEnterHolder; + + enum Stage { + STAGE_CREATING, + STAGE_READYFORMANAGEDCODE, + STAGE_ACTIVE, + STAGE_OPEN, + STAGE_UNLOAD_REQUESTED, + STAGE_EXITING, + STAGE_EXITED, + STAGE_FINALIZING, + STAGE_FINALIZED, + STAGE_HANDLETABLE_NOACCESS, + STAGE_CLEARED, + STAGE_COLLECTED, + STAGE_CLOSED + }; + void SetStage(Stage stage) + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + MODE_ANY; + } + CONTRACTL_END; + STRESS_LOG2(LF_APPDOMAIN, LL_INFO100,"Updating AD stage, ADID=%d, stage=%d\n",GetId().m_dwId,stage); + TESTHOOKCALL(AppDomainStageChanged(GetId().m_dwId,m_Stage,stage)); + Stage lastStage=m_Stage; + while (lastStage !=stage) + lastStage = (Stage)FastInterlockCompareExchange((LONG*)&m_Stage,stage,lastStage); + }; + void Exit(BOOL fRunFinalizers, BOOL fAsyncExit); + void Close(); + void ClearGCRoots(); + void ClearGCHandles(); + void HandleAsyncPinHandles(); + void UnwindThreads(); + // Return TRUE if EE is stopped + // Return FALSE if more work is needed + BOOL StopEEAndUnwindThreads(unsigned int retryCount, BOOL *pFMarkUnloadRequestThread); + + // Use Rude Abort to unload the domain. + BOOL m_fRudeUnload; + + Thread *m_pUnloadRequestThread; + ADUnloadSink* m_ADUnloadSink; + BOOL m_bForceGCOnUnload; + BOOL m_bUnloadingFromUnloadEvent; + AppDomainLoaderAllocator m_LoaderAllocator; + + // List of unloaded LoaderAllocators, protected by code:GetLoaderAllocatorReferencesLock (for now) + LoaderAllocator * m_pDelayedLoaderAllocatorUnloadList; + +public: + + // Register the loader allocator for deletion in code:ShutdownFreeLoaderAllocators. + void RegisterLoaderAllocatorForDeletion(LoaderAllocator * pLoaderAllocator); + + AppDomain * m_pNextInDelayedUnloadList; + + void SetForceGCOnUnload(BOOL bSet) + { + m_bForceGCOnUnload=bSet; + } + + void SetUnloadingFromUnloadEvent() + { + m_bUnloadingFromUnloadEvent=TRUE; + } + + BOOL IsUnloadingFromUnloadEvent() + { + return m_bUnloadingFromUnloadEvent; + } + + void SetRudeUnload() + { + LIMITED_METHOD_CONTRACT; + + m_fRudeUnload = TRUE; + } + + BOOL IsRudeUnload() + { + LIMITED_METHOD_CONTRACT; + + return m_fRudeUnload; + } + + ADUnloadSink* GetADUnloadSink(); + ADUnloadSink* GetADUnloadSinkForUnload(); + void SetUnloadRequestThread(Thread *pThread) + { + LIMITED_METHOD_CONTRACT; + + m_pUnloadRequestThread = pThread; + } + + Thread *GetUnloadRequestThread() + { + LIMITED_METHOD_CONTRACT; + + return m_pUnloadRequestThread; + } + +public: + void SetGCRefPoint(int gccounter) + { + LIMITED_METHOD_CONTRACT; + m_LoaderAllocator.SetGCRefPoint(gccounter); + } + int GetGCRefPoint() + { + LIMITED_METHOD_CONTRACT; + return m_LoaderAllocator.GetGCRefPoint(); + } + + static USHORT GetOffsetOfId() + { + LIMITED_METHOD_CONTRACT; + size_t ofs = offsetof(class AppDomain, m_dwId); + _ASSERTE(FitsInI2(ofs)); + return (USHORT)ofs; + } + + + void AddMemoryPressure(); + void RemoveMemoryPressure(); + void Unload(BOOL fForceUnload); + static HRESULT UnloadById(ADID Id, BOOL fSync, BOOL fExceptionsPassThrough=FALSE); + static HRESULT UnloadWait(ADID Id, ADUnloadSink* pSink); +#ifdef FEATURE_TESTHOOKS + static HRESULT UnloadWaitNoCatch(ADID Id, ADUnloadSink* pSink); +#endif + static void ResetUnloadRequestThread(ADID Id); + + void UnlinkClass(MethodTable *pMT); + + typedef Holder UnloadHolder; + Assembly *GetRootAssembly() + { + LIMITED_METHOD_CONTRACT; + return m_pRootAssembly; + } + +#ifndef DACCESS_COMPILE + void SetRootAssembly(Assembly *pAssembly) + { + LIMITED_METHOD_CONTRACT; + m_pRootAssembly = pAssembly; + } +#endif + +private: + SString m_friendlyName; + PTR_Assembly m_pRootAssembly; + + // General purpose flags. + DWORD m_dwFlags; + + // When an application domain is created the ref count is artifically incremented + // by one. For it to hit zero an explicit close must have happened. + LONG m_cRef; // Ref count. + + PTR_IApplicationSecurityDescriptor m_pSecDesc; // Application Security Descriptor + + OBJECTHANDLE m_ExposedObject; + +#ifdef FEATURE_LOADER_OPTIMIZATION + // Indicates where assemblies will be loaded for + // this domain. By default all assemblies are loaded into the domain. + // There are two additional settings, all + // assemblies can be loaded into the shared domain or assemblies + // that are strong named are loaded into the shared area. + SharePolicy m_SharePolicy; +#endif + + IUnknown *m_pComIPForExposedObject; + + // Hash table that maps a clsid to a type + PtrHashMap m_clsidHash; + +#ifdef FEATURE_COMINTEROP + // Hash table that maps WinRT class names to MethodTables. + PTR_NameToTypeMapTable m_pNameToTypeMap; + UINT m_vNameToTypeMapVersion; + + UINT m_nEpoch; // incremented each time m_pNameToTypeMap is enumerated + + // Hash table that remembers the last cached WinRT factory object per type per appdomain. + WinRTFactoryCache *m_pWinRTFactoryCache; + + // The wrapper cache for this domain - it has its own CCacheLineAllocator on a per domain basis + // to allow the domain to go away and eventually kill the memory when all refs are gone + ComCallWrapperCache *m_pComCallWrapperCache; + + // this cache stores the RCWs in this domain + RCWCache *m_pRCWCache; + + // this cache stores the RCW -> CCW references in this domain + RCWRefCache *m_pRCWRefCache; + + // The method table used for LicenseInteropHelper + MethodTable* m_pLicenseInteropHelperMT; +#endif // FEATURE_COMINTEROP + + AssemblySink* m_pAsyncPool; // asynchronous retrival object pool (only one is kept) + + // The index of this app domain among existing app domains (starting from 1) + ADIndex m_dwIndex; + + // The thread-pool index of this app domain among existing app domains (starting from 1) + TPIndex m_tpIndex; + +#ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING + ULONGLONG* m_pullAllocBytes; + ULONGLONG* m_pullSurvivedBytes; + DWORD m_dwNumHeaps; + ULONGLONG m_ullLastEtwAllocBytes; + // Total processor time (user and kernel) utilized by threads running in this AppDomain so far. May not + // account for threads currently executing in the AppDomain until a call to QueryProcessorUsage() is + // made. + Volatile m_ullTotalProcessorUsage; +#endif //FEATURE_APPDOMAIN_RESOURCE_MONITORING + +#ifdef _DEBUG + struct ThreadTrackInfo; + typedef CDynArray ThreadTrackInfoList; + ThreadTrackInfoList *m_pThreadTrackInfoList; + DWORD m_TrackSpinLock; +#endif + + + // IL stub cache with fabricated MethodTable parented by a random module in this AD. + ILStubCache m_ILStubCache; + + // U->M thunks created in this domain and not associated with a delegate. + // The cache is keyed by MethodDesc pointers. + UMEntryThunkCache *m_pUMEntryThunkCache; + + // The number of times we have entered this AD + ULONG m_dwThreadEnterCount; + // The number of threads that have entered this AD, for ADU only + ULONG m_dwThreadsStillInAppDomain; + + Volatile m_Stage; + + // The default context for this domain + Context *m_pDefaultContext; + + SString m_applicationBase; + SString m_privateBinPaths; + SString m_configFile; + + ArrayList m_failedAssemblies; + + DomainAssembly * m_anonymouslyHostedDynamicMethodsAssembly; + +#ifdef _DEBUG + Volatile m_dwIterHolders; + Volatile m_dwRefTakers; + Volatile m_dwCreationHolders; +#endif + + // + // DAC iterator for failed assembly loads + // + class FailedAssemblyIterator + { + ArrayList::Iterator m_i; + + public: + BOOL Next() + { + WRAPPER_NO_CONTRACT; + return m_i.Next(); + } + FailedAssembly *GetFailedAssembly() + { + WRAPPER_NO_CONTRACT; + return dac_cast(m_i.GetElement()); + } + SIZE_T GetIndex() + { + WRAPPER_NO_CONTRACT; + return m_i.GetIndex(); + } + + private: + friend class AppDomain; + // Cannot have constructor so this iterator can be used inside a union + static FailedAssemblyIterator Create(AppDomain *pDomain) + { + WRAPPER_NO_CONTRACT; + FailedAssemblyIterator i; + + i.m_i = pDomain->m_failedAssemblies.Iterate(); + return i; + } + }; + friend class FailedAssemblyIterator; + + FailedAssemblyIterator IterateFailedAssembliesEx() + { + WRAPPER_NO_CONTRACT; + return FailedAssemblyIterator::Create(this); + } + + //--------------------------------------------------------- + // Stub caches for Method stubs + //--------------------------------------------------------- + +#ifdef FEATURE_FUSION + void TurnOnBindingRedirects(); +#endif +public: + +#if defined(FEATURE_HOST_ASSEMBLY_RESOLVER) +private: + Volatile m_fIsBindingModelLocked; +public: + BOOL IsHostAssemblyResolverInUse(); + BOOL IsBindingModelLocked(); + BOOL LockBindingModel(); +#endif // defined(FEATURE_HOST_ASSEMBLY_RESOLVER) + + UMEntryThunkCache *GetUMEntryThunkCache(); + + ILStubCache* GetILStubCache() + { + LIMITED_METHOD_CONTRACT; + return &m_ILStubCache; + } + + static AppDomain* GetDomain(ILStubCache* pILStubCache) + { + return CONTAINING_RECORD(pILStubCache, AppDomain, m_ILStubCache); + } + + enum { + CONTEXT_INITIALIZED = 0x0001, + USER_CREATED_DOMAIN = 0x0002, // created by call to AppDomain.CreateDomain + ALLOCATEDCOM = 0x0008, + LOAD_SYSTEM_ASSEMBLY_EVENT_SENT = 0x0040, + REMOTING_CONFIGURED_FOR_DOMAIN = 0x0100, + COMPILATION_DOMAIN = 0x0400, // Are we ngenning? + APP_DOMAIN_CAN_BE_UNLOADED = 0x0800, // if need extra bits, can derive this at runtime + ORPHANED_LOCKS = 0x1000, // Orphaned locks exist in this appdomain. + PASSIVE_DOMAIN = 0x2000, // Can we execute code in this AppDomain + VERIFICATION_DOMAIN = 0x4000, // This is a verification domain + ILLEGAL_VERIFICATION_DOMAIN = 0x8000, // This can't be a verification domain + IGNORE_UNHANDLED_EXCEPTIONS = 0x10000, // AppDomain was created using the APPDOMAIN_IGNORE_UNHANDLED_EXCEPTIONS flag + ENABLE_PINVOKE_AND_CLASSIC_COMINTEROP = 0x20000, // AppDomain was created using the APPDOMAIN_ENABLE_PINVOKE_AND_CLASSIC_COMINTEROP flag +#ifdef FEATURE_CORECLR + ENABLE_SKIP_PLAT_CHECKS = 0x200000, // Skip various assembly checks (like platform check) + ENABLE_ASSEMBLY_LOADFILE = 0x400000, // Allow Assembly.LoadFile in CoreCLR + DISABLE_TRANSPARENCY_ENFORCEMENT= 0x800000, // Disable enforcement of security transparency rules +#endif + }; + + SecurityContext *m_pSecContext; + + AssemblySpecBindingCache m_AssemblyCache; + DomainAssemblyCache m_UnmanagedCache; + size_t m_MemoryPressure; + + SString m_AppDomainManagerAssembly; + SString m_AppDomainManagerType; + BOOL m_fAppDomainManagerSetInConfig; + EInitializeNewDomainFlags m_dwAppDomainManagerInitializeDomainFlags; + +#ifdef FEATURE_CORECLR + ArrayList m_NativeDllSearchDirectories; +#endif + BOOL m_ReversePInvokeCanEnter; + bool m_ForceTrivialWaitOperations; + // Section to support AD unload due to escalation +public: + static void CreateADUnloadWorker(); + + static void CreateADUnloadStartEvent(); + + static DWORD WINAPI ADUnloadThreadStart(void *args); + + // Default is safe unload with test hook + void EnableADUnloadWorker(); + + // If called to handle stack overflow, we can not set event, since the thread has limit stack. + void EnableADUnloadWorker(EEPolicy::AppDomainUnloadTypes type, BOOL fHasStack = TRUE); + + static void EnableADUnloadWorkerForThreadAbort(); + static void EnableADUnloadWorkerForFinalizer(); + static void EnableADUnloadWorkerForCollectedADCleanup(); + + BOOL IsUnloadRequested() + { + LIMITED_METHOD_CONTRACT; + + return (m_Stage == STAGE_UNLOAD_REQUESTED); + } + +#ifdef FEATURE_CORECLR + BOOL IsImageFromTrustedPath(PEImage* pImage); + BOOL IsImageFullyTrusted(PEImage* pImage); +#endif + +#ifdef FEATURE_TYPEEQUIVALENCE +private: + VolatilePtr m_pTypeEquivalenceTable; + CrstExplicitInit m_TypeEquivalenceCrst; +public: + TypeEquivalenceHashTable * GetTypeEquivalenceCache(); +#endif + + private: + static void ADUnloadWorkerHelper(AppDomain *pDomain); + static CLREvent * g_pUnloadStartEvent; + +#ifdef DACCESS_COMPILE +public: + virtual void EnumMemoryRegions(CLRDataEnumMemoryFlags flags, + bool enumThis); +#endif + +#ifdef FEATURE_MULTICOREJIT + +private: + MulticoreJitManager m_MulticoreJitManager; + +public: + MulticoreJitManager & GetMulticoreJitManager() + { + LIMITED_METHOD_CONTRACT; + + return m_MulticoreJitManager; + } + +#endif + +#ifdef FEATURE_COMINTEROP + +private: +#ifdef FEATURE_REFLECTION_ONLY_LOAD + // ReflectionOnly WinRT binder and its TypeCache (only in classic = non-AppX; the scenario is not supported in AppX) + CLRPrivBinderReflectionOnlyWinRT * m_pReflectionOnlyWinRtBinder; + CLRPrivTypeCacheReflectionOnlyWinRT * m_pReflectionOnlyWinRtTypeCache; +#endif // FEATURE_REFLECTION_ONLY_LOAD + +#endif //FEATURE_COMINTEROP + +public: +#ifndef FEATURE_CORECLR + BOOL m_bUseOsSorting; + DWORD m_sortVersion; + COMNlsCustomSortLibrary *m_pCustomSortLibrary; +#if _DEBUG + BOOL m_bSortingInitialized; +#endif // _DEBUG + COMNlsHashProvider *m_pNlsHashProvider; +#endif // !FEATURE_CORECLR + +private: + // This is the root-level default load context root binder. If null, then + // the Fusion binder is used; otherwise this binder is used. + ReleaseHolder m_pLoadContextHostBinder; + + // ------------------------- + // IMPORTANT! + // The shared and designer context binders are ONLY to be used in tool + // scenarios. There are known issues where use of these binders will + // cause application crashes, and interesting behaviors. + // ------------------------- + + // This is the default designer shared context root binder. + // This is used as the parent binder for ImmersiveDesignerContextBinders + ReleaseHolder m_pSharedContextHostBinder; + + // This is the current context root binder. + // Normally, this variable is immutable for appdomain lifetime, but in designer scenarios + // it may be replaced by designer context binders + Volatile m_pCurrentContextHostBinder; + +public: + // Returns the current hosted binder, or null if none available. + inline + ICLRPrivBinder * GetCurrentLoadContextHostBinder() const + { + LIMITED_METHOD_CONTRACT; + return m_pCurrentContextHostBinder; + } + + // Returns the shared context binder, or null if none available. + inline + ICLRPrivBinder * GetSharedContextHostBinder() const + { + LIMITED_METHOD_CONTRACT; + return m_pSharedContextHostBinder; + } + + // Returns the load context binder, or null if none available. + inline + ICLRPrivBinder * GetLoadContextHostBinder() const + { + LIMITED_METHOD_CONTRACT; + return m_pLoadContextHostBinder; + } + +#ifndef DACCESS_COMPILE + + // This is only called from the ImmersiveDesignerContext code + // It is protected with a managed monitor lock + inline + void SetSharedContextHostBinder(ICLRPrivBinder * pBinder) + { + LIMITED_METHOD_CONTRACT; + pBinder->AddRef(); + m_pSharedContextHostBinder = pBinder; + } + + // This is called from CorHost2's implementation of ICLRPrivRuntime::CreateAppDomain. + // Should only be called during AppDomain creation. + inline + void SetLoadContextHostBinder(ICLRPrivBinder * pBinder) + { + LIMITED_METHOD_CONTRACT; + pBinder->AddRef(); + m_pLoadContextHostBinder = m_pCurrentContextHostBinder = pBinder; + } + + inline + void SetCurrentContextHostBinder(ICLRPrivBinder * pBinder) + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + } + CONTRACTL_END; + + LockHolder lh(this); + +#ifdef FEATURE_COMINTEROP + if (m_pNameToTypeMap != nullptr) + { + delete m_pNameToTypeMap; + m_pNameToTypeMap = nullptr; + } + + m_vNameToTypeMapVersion++; +#endif + + m_pCurrentContextHostBinder = pBinder; + } + +#endif // DACCESS_COMPILE + + // Indicates that a hosted binder is present. + inline + bool HasLoadContextHostBinder() + { + LIMITED_METHOD_CONTRACT; + return m_pLoadContextHostBinder != nullptr; + } + + class ComInterfaceReleaseList + { + SArray m_objects; + public: + ~ComInterfaceReleaseList() + { + WRAPPER_NO_CONTRACT; + + for (COUNT_T i = 0; i < m_objects.GetCount(); i++) + { + IUnknown *pItf = *(m_objects.GetElements() + i); + if (pItf != nullptr) + pItf->Release(); + } + } + + // Append to the list of object to free. Only use under the AppDomain "LockHolder(pAppDomain)" + void Append(IUnknown *pInterfaceToRelease) + { + WRAPPER_NO_CONTRACT; + m_objects.Append(pInterfaceToRelease); + } + } AppDomainInterfaceReleaseList; + +private: + //----------------------------------------------------------- + // Static ICLRPrivAssembly -> DomainAssembly mapping functions. + // This map does not maintain a reference count to either key or value. + // PEFile maintains a reference count on the ICLRPrivAssembly through its code:PEFile::m_pHostAssembly field. + // It is removed from this hash table by code:DomainAssembly::~DomainAssembly. + struct HostAssemblyHashTraits : public DefaultSHashTraits + { + public: + typedef PTR_ICLRPrivAssembly key_t; + + static key_t GetKey(element_t const & elem) + { + STATIC_CONTRACT_WRAPPER; + return elem->GetFile()->GetHostAssembly(); + } + + static BOOL Equals(key_t key1, key_t key2) + { + LIMITED_METHOD_CONTRACT; + return dac_cast(key1) == dac_cast(key2); + } + + static count_t Hash(key_t key) + { + STATIC_CONTRACT_LIMITED_METHOD; + //return reinterpret_cast(dac_cast(key)); + return (count_t)(dac_cast(key)); + } + + static const element_t Null() { return NULL; } + static const element_t Deleted() { return (element_t)(TADDR)-1; } + static bool IsNull(const element_t & e) { return e == NULL; } + static bool IsDeleted(const element_t & e) { return dac_cast(e) == (TADDR)-1; } + }; + + struct OriginalFileHostAssemblyHashTraits : public HostAssemblyHashTraits + { + public: + static key_t GetKey(element_t const & elem) + { + STATIC_CONTRACT_WRAPPER; + return elem->GetOriginalFile()->GetHostAssembly(); + } + }; + + typedef SHash HostAssemblyMap; + typedef SHash OriginalFileHostAssemblyMap; + HostAssemblyMap m_hostAssemblyMap; + OriginalFileHostAssemblyMap m_hostAssemblyMapForOrigFile; + CrstExplicitInit m_crstHostAssemblyMap; + // Lock to serialize all Add operations (in addition to the "read-lock" above) + CrstExplicitInit m_crstHostAssemblyMapAdd; + +public: + // Returns DomainAssembly. + PTR_DomainAssembly FindAssembly(PTR_ICLRPrivAssembly pHostAssembly); + +#ifndef DACCESS_COMPILE +private: + friend void DomainAssembly::Allocate(); + friend DomainAssembly::~DomainAssembly(); + + // Called from DomainAssembly::Begin. + void PublishHostedAssembly( + DomainAssembly* pAssembly); + + // Called from DomainAssembly::UpdatePEFile. + void UpdatePublishHostedAssembly( + DomainAssembly* pAssembly, + PTR_PEFile pFile); + + // Called from DomainAssembly::~DomainAssembly + void UnPublishHostedAssembly( + DomainAssembly* pAssembly); +#endif // DACCESS_COMPILE + +#ifdef FEATURE_PREJIT + friend void DomainFile::InsertIntoDomainFileWithNativeImageList(); + Volatile m_pDomainFileWithNativeImageList; +public: + DomainFile *GetDomainFilesWithNativeImagesList() + { + LIMITED_METHOD_CONTRACT; + return m_pDomainFileWithNativeImageList; + } +#endif +}; // class AppDomain + + +// This holder is to be used to take a reference to make sure AppDomain* is still valid +// Please do not use if you are aleady ADU-safe +typedef Wrapper AppDomainRefTaker; + +// Just a ref holder +typedef ReleaseHolder AppDomainRefHolder; + +// This class provides a way to access AppDomain by ID +// without risking the appdomain getting invalid in the process +class AppDomainFromIDHolder +{ +public: + enum SyncType + { + SyncType_GC, // Prevents AD from being unloaded by forbidding GC for the lifetime of the object + SyncType_ADLock // Prevents AD from being unloaded by requiring ownership of DomainLock for the lifetime of the object + }; +protected: + AppDomain* m_pDomain; +#ifdef _DEBUG + BOOL m_bAcquired; + BOOL m_bChecked; + SyncType m_type; +#endif +public: + DEBUG_NOINLINE AppDomainFromIDHolder(ADID adId, BOOL bUnsafePoint, SyncType synctype=SyncType_GC); + DEBUG_NOINLINE AppDomainFromIDHolder(SyncType synctype=SyncType_GC); + DEBUG_NOINLINE ~AppDomainFromIDHolder(); + + void* GetAddress() { return m_pDomain; } // Used to get an identfier for ETW + void Assign(ADID adId, BOOL bUnsafePoint); + void ThrowIfUnloaded(); + void Release(); + BOOL IsUnloaded() + { + LIMITED_METHOD_CONTRACT; +#ifdef _DEBUG + m_bChecked=TRUE; + if (m_pDomain==NULL) + { + // no need to enforce anything + Release(); + } +#endif + return m_pDomain==NULL; + }; + AppDomain* operator->(); +}; // class AppDomainFromIDHolder + + + +typedef VPTR(class SystemDomain) PTR_SystemDomain; + +class SystemDomain : public BaseDomain +{ + friend class AppDomainNative; + friend class AppDomainIterator; + friend class UnsafeAppDomainIterator; + friend class ClrDataAccess; + friend class AppDomainFromIDHolder; + friend Frame *Thread::IsRunningIn(AppDomain* pDomain, int *count); + + VPTR_VTABLE_CLASS(SystemDomain, BaseDomain) + VPTR_UNIQUE(VPTR_UNIQUE_SystemDomain) + static AppDomain *GetAppDomainAtId(ADID indx); + +public: + static PTR_LoaderAllocator GetGlobalLoaderAllocator(); + virtual PTR_LoaderAllocator GetLoaderAllocator() { WRAPPER_NO_CONTRACT; return GetGlobalLoaderAllocator(); } + static AppDomain* GetAppDomainFromId(ADID indx,DWORD ADValidityKind) + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + AppDomain* pRetVal; + if (indx.m_dwId==DefaultADID) + pRetVal= SystemDomain::System()->DefaultDomain(); + else + pRetVal= GetAppDomainAtId(indx); +#ifdef _DEBUG + // Only call CheckADValidity in DEBUG builds for non-NULL return values + if (pRetVal != NULL) + CheckADValidity(pRetVal, ADValidityKind); +#endif + return pRetVal; + } + //**************************************************************************************** + // + // To be run during the initial start up of the EE. This must be + // performed prior to any class operations. + static void Attach(); + + //**************************************************************************************** + // + // To be run during shutdown. This must be done after all operations + // that require the use of system classes (i.e., exceptions). + // DetachBegin stops all domains, while DetachEnd deallocates domain resources. + static void DetachBegin(); + + //**************************************************************************************** + // + // To be run during shutdown. This must be done after all operations + // that require the use of system classes (i.e., exceptions). + // DetachBegin stops release resources held by systemdomain and the default domain. + static void DetachEnd(); + + //**************************************************************************************** + // + // Initializes and shutdowns the single instance of the SystemDomain + // in the EE +#ifndef DACCESS_COMPILE + void *operator new(size_t size, void *pInPlace); + void operator delete(void *pMem); +#endif + void Init(); + void Stop(); + void Terminate(); + static void LazyInitGlobalStringLiteralMap(); + + //**************************************************************************************** + // + // Load the base system classes, these classes are required before + // any other classes are loaded + void LoadBaseSystemClasses(); + + AppDomain* DefaultDomain() + { + LIMITED_METHOD_DAC_CONTRACT; + + return m_pDefaultDomain; + } + + // Notification when an assembly is loaded into the system domain + void OnAssemblyLoad(Assembly *assem); + + //**************************************************************************************** + // + // Global Static to get the one and only system domain + static SystemDomain * System() + { + LIMITED_METHOD_DAC_CONTRACT; + + return m_pSystemDomain; + } + + static PEAssembly* SystemFile() + { + WRAPPER_NO_CONTRACT; + + _ASSERTE(m_pSystemDomain); + return System()->m_pSystemFile; + } + + static Assembly* SystemAssembly() + { + WRAPPER_NO_CONTRACT; + + return System()->m_pSystemAssembly; + } + + static Module* SystemModule() + { + WRAPPER_NO_CONTRACT; + + return SystemAssembly()->GetManifestModule(); + } + + static BOOL IsSystemLoaded() + { + WRAPPER_NO_CONTRACT; + + return System()->m_pSystemAssembly != NULL; + } + +#ifndef DACCESS_COMPILE + static GlobalStringLiteralMap *GetGlobalStringLiteralMap() + { + WRAPPER_NO_CONTRACT; + + if (m_pGlobalStringLiteralMap == NULL) + { + SystemDomain::LazyInitGlobalStringLiteralMap(); + } + _ASSERTE(m_pGlobalStringLiteralMap); + return m_pGlobalStringLiteralMap; + } + static GlobalStringLiteralMap *GetGlobalStringLiteralMapNoCreate() + { + LIMITED_METHOD_CONTRACT; + + _ASSERTE(m_pGlobalStringLiteralMap); + return m_pGlobalStringLiteralMap; + } +#endif // DACCESS_COMPILE + +#ifndef FEATURE_CORECLR + static void ExecuteMainMethod(HMODULE hMod, __in_opt LPWSTR path = NULL); +#endif + static void ActivateApplication(int *pReturnValue); + + static void InitializeDefaultDomain(BOOL allowRedirects, ICLRPrivBinder * pBinder = NULL); + static void SetupDefaultDomain(); + static HRESULT SetupDefaultDomainNoThrow(); + +#if defined(FEATURE_COMINTEROP_APARTMENT_SUPPORT) && !defined(CROSSGEN_COMPILE) + static Thread::ApartmentState GetEntryPointThreadAptState(IMDInternalImport* pScope, mdMethodDef mdMethod); + static void SetThreadAptState(IMDInternalImport* pScope, Thread::ApartmentState state); +#endif + static BOOL SetGlobalSharePolicyUsingAttribute(IMDInternalImport* pScope, mdMethodDef mdMethod); + +#ifdef FEATURE_MIXEDMODE + static HRESULT RunDllMain(HINSTANCE hInst, DWORD dwReason, LPVOID lpReserved); +#endif // FEATURE_MIXEDMODE + + //**************************************************************************************** + // + // Use an already exising & inited Application Domain (e.g. a subclass). + static void LoadDomain(AppDomain *pDomain); + +#ifndef DACCESS_COMPILE + static void MakeUnloadable(AppDomain* pApp) + { + WRAPPER_NO_CONTRACT; + System()->AddDomain(pApp); + pApp->SetCanUnload(); + } +#endif // DACCESS_COMPILE + + //**************************************************************************************** + // Methods used to get the callers module and hence assembly and app domain. + __declspec(deprecated("This method is deprecated, use the version that takes a StackCrawlMark instead")) + static Module* GetCallersModule(int skip); + static MethodDesc* GetCallersMethod(StackCrawlMark* stackMark, AppDomain **ppAppDomain = NULL); + static MethodTable* GetCallersType(StackCrawlMark* stackMark, AppDomain **ppAppDomain = NULL); + static Module* GetCallersModule(StackCrawlMark* stackMark, AppDomain **ppAppDomain = NULL); + static Assembly* GetCallersAssembly(StackCrawlMark* stackMark, AppDomain **ppAppDomain = NULL); + + static bool IsReflectionInvocationMethod(MethodDesc* pMeth); + +#ifndef DACCESS_COMPILE + //**************************************************************************************** + // Returns the domain associated with the current context. (this can only be a child domain) + static inline AppDomain * GetCurrentDomain() + { + WRAPPER_NO_CONTRACT; + return ::GetAppDomain(); + } +#endif //!DACCESS_COMPILE + +#ifdef DEBUGGING_SUPPORTED + //**************************************************************************************** + // Debugger/Publisher helper function to indicate creation of new app domain to debugger + // and publishing it in the IPC block + static void PublishAppDomainAndInformDebugger (AppDomain *pDomain); +#endif // DEBUGGING_SUPPORTED + + //**************************************************************************************** + // Helper function to remove a domain from the system + BOOL RemoveDomain(AppDomain* pDomain); // Does not decrement the reference + +#ifdef PROFILING_SUPPORTED + //**************************************************************************************** + // Tell profiler about system created domains which are created before the profiler is + // actually activated. + static void NotifyProfilerStartup(); + + //**************************************************************************************** + // Tell profiler at shutdown that system created domains are going away. They are not + // torn down using the normal sequence. + static HRESULT NotifyProfilerShutdown(); +#endif // PROFILING_SUPPORTED + + //**************************************************************************************** + // return the dev path +#ifdef FEATURE_FUSION + void GetDevpathW(__out_ecount_opt(1) LPWSTR* pPath, DWORD* pSize); +#endif + +#ifndef DACCESS_COMPILE + void IncrementNumAppDomains () + { + LIMITED_METHOD_CONTRACT; + + s_dNumAppDomains++; + } + + void DecrementNumAppDomains () + { + LIMITED_METHOD_CONTRACT; + + s_dNumAppDomains--; + } + + ULONG GetNumAppDomains () + { + LIMITED_METHOD_CONTRACT; + + return s_dNumAppDomains; + } +#endif // DACCESS_COMPILE + + // + // AppDomains currently have both an index and an ID. The + // index is "densely" assigned; indices are reused as domains + // are unloaded. The Id's on the other hand, are not reclaimed + // so may be sparse. + // + // Another important difference - it's OK to call GetAppDomainAtId for + // an unloaded domain (it will return NULL), while GetAppDomainAtIndex + // will assert if the domain is unloaded. + // + // @todo: + // I'm not really happy with this situation, but + // (a) we need an ID for a domain which will last the process lifetime for the + // remoting code. + // (b) we need a dense ID, for the handle table index. + // So for now, I'm leaving both, but hopefully in the future we can come up + // with something better. + // + + static ADIndex GetNewAppDomainIndex(AppDomain * pAppDomain); + static void ReleaseAppDomainIndex(ADIndex indx); + static PTR_AppDomain GetAppDomainAtIndex(ADIndex indx); + static PTR_AppDomain TestGetAppDomainAtIndex(ADIndex indx); + static DWORD GetCurrentAppDomainMaxIndex() + { + WRAPPER_NO_CONTRACT; + + ArrayListStatic* list = (ArrayListStatic *)&m_appDomainIndexList; + PREFIX_ASSUME(list!=NULL); + return list->GetCount(); + } + + static ADID GetNewAppDomainId(AppDomain *pAppDomain); + static void ReleaseAppDomainId(ADID indx); + +#ifndef DACCESS_COMPILE + static ADID GetCurrentAppDomainMaxId() { ADID id; id.m_dwId=m_appDomainIdList.GetCount(); return id;} +#endif // DACCESS_COMPILE + + +#ifndef DACCESS_COMPILE + DWORD RequireAppDomainCleanup() + { + LIMITED_METHOD_CONTRACT; + return m_pDelayedUnloadList != 0 || m_pDelayedUnloadListOfLoaderAllocators != 0; + } + + void AddToDelayedUnloadList(AppDomain* pDomain, BOOL bAsync) + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + m_UnloadIsAsync = bAsync; + + CrstHolder lh(&m_DelayedUnloadCrst); + pDomain->m_pNextInDelayedUnloadList=m_pDelayedUnloadList; + m_pDelayedUnloadList=pDomain; + if (m_UnloadIsAsync) + { + pDomain->AddRef(); + int iGCRefPoint=GCHeap::GetGCHeap()->CollectionCount(GCHeap::GetGCHeap()->GetMaxGeneration()); + if (GCHeap::GetGCHeap()->IsGCInProgress()) + iGCRefPoint++; + pDomain->SetGCRefPoint(iGCRefPoint); + } + } + + void AddToDelayedUnloadList(LoaderAllocator * pAllocator) + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + CrstHolder lh(&m_DelayedUnloadCrst); + pAllocator->m_pLoaderAllocatorDestroyNext=m_pDelayedUnloadListOfLoaderAllocators; + m_pDelayedUnloadListOfLoaderAllocators=pAllocator; + + int iGCRefPoint=GCHeap::GetGCHeap()->CollectionCount(GCHeap::GetGCHeap()->GetMaxGeneration()); + if (GCHeap::GetGCHeap()->IsGCInProgress()) + iGCRefPoint++; + pAllocator->SetGCRefPoint(iGCRefPoint); + } + + void ClearCollectedDomains(); + void ProcessClearingDomains(); + void ProcessDelayedUnloadDomains(); + + static void SetUnloadInProgress(AppDomain *pDomain) + { + WRAPPER_NO_CONTRACT; + + _ASSERTE(m_pAppDomainBeingUnloaded == NULL); + m_pAppDomainBeingUnloaded = pDomain; + m_dwIndexOfAppDomainBeingUnloaded = pDomain->GetIndex(); + } + + static void SetUnloadDomainCleared() + { + LIMITED_METHOD_CONTRACT; + + // about to delete, so clear this pointer so nobody uses it + m_pAppDomainBeingUnloaded = NULL; + } + static void SetUnloadComplete() + { + LIMITED_METHOD_CONTRACT; + + // should have already cleared the AppDomain* prior to delete + // either we succesfully unloaded and cleared or we failed and restored the ID + _ASSERTE(m_pAppDomainBeingUnloaded == NULL && m_dwIndexOfAppDomainBeingUnloaded.m_dwIndex != 0 + || m_pAppDomainBeingUnloaded && SystemDomain::GetAppDomainAtId(m_pAppDomainBeingUnloaded->GetId()) != NULL); + m_pAppDomainBeingUnloaded = NULL; + m_pAppDomainUnloadingThread = NULL; + } + + static AppDomain *AppDomainBeingUnloaded() + { + LIMITED_METHOD_CONTRACT; + return m_pAppDomainBeingUnloaded; + } + + static ADIndex IndexOfAppDomainBeingUnloaded() + { + LIMITED_METHOD_CONTRACT; + return m_dwIndexOfAppDomainBeingUnloaded; + } + + static void SetUnloadRequestingThread(Thread *pRequestingThread) + { + LIMITED_METHOD_CONTRACT; + m_pAppDomainUnloadRequestingThread = pRequestingThread; + } + + static Thread *GetUnloadRequestingThread() + { + LIMITED_METHOD_CONTRACT; + return m_pAppDomainUnloadRequestingThread; + } + + static void SetUnloadingThread(Thread *pUnloadingThread) + { + LIMITED_METHOD_CONTRACT; + m_pAppDomainUnloadingThread = pUnloadingThread; + } + + static Thread *GetUnloadingThread() + { + LIMITED_METHOD_CONTRACT; + return m_pAppDomainUnloadingThread; + } + + static void EnumAllStaticGCRefs(promote_func* fn, ScanContext* sc); + +#endif // DACCESS_COMPILE + +#ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING + // The *AD* methods are what we got from tracing through EE roots. + // RecordTotalSurvivedBytes is the total promoted from a GC. + static void ResetADSurvivedBytes(); + static ULONGLONG GetADSurvivedBytes(); + static void RecordTotalSurvivedBytes(size_t totalSurvivedBytes); + static ULONGLONG GetTotalSurvivedBytes() + { + LIMITED_METHOD_CONTRACT; + return m_totalSurvivedBytes; + } +#endif //FEATURE_APPDOMAIN_RESOURCE_MONITORING + + //**************************************************************************************** + // Routines to deal with the base library (currently mscorlib.dll) + LPCWSTR BaseLibrary() + { + WRAPPER_NO_CONTRACT; + + return m_BaseLibrary; + } + +#ifndef DACCESS_COMPILE + BOOL IsBaseLibrary(SString &path) + { + WRAPPER_NO_CONTRACT; + + // See if it is the installation path to mscorlib + if (path.EqualsCaseInsensitive(m_BaseLibrary, PEImage::GetFileSystemLocale())) + return TRUE; + + // Or, it might be the GAC location of mscorlib + if (System()->SystemAssembly() != NULL + && path.EqualsCaseInsensitive(System()->SystemAssembly()->GetManifestFile()->GetPath(), + PEImage::GetFileSystemLocale())) + return TRUE; + + return FALSE; + } + + BOOL IsBaseLibrarySatellite(SString &path) + { + WRAPPER_NO_CONTRACT; + + // See if it is the installation path to mscorlib.resources + SString s(SString::Ascii,g_psBaseLibrarySatelliteAssemblyName); + if (path.EqualsCaseInsensitive(s, PEImage::GetFileSystemLocale())) + return TRUE; + + // workaround! Must implement some code to do this string comparison for + // mscorlib.resources in a culture-specific directory in the GAC. + + /* + // Or, it might be the GAC location of mscorlib.resources + if (System()->SystemAssembly() != NULL + && path.EqualsCaseInsensitive(System()->SystemAssembly()->GetManifestFile()->GetPath(), + PEImage::GetFileSystemLocale())) + return TRUE; + */ + + return FALSE; + } +#endif // DACCESS_COMPILE + + // Return the system directory + LPCWSTR SystemDirectory() + { + WRAPPER_NO_CONTRACT; + + return m_SystemDirectory; + } + +private: + + //**************************************************************************************** + // Helper function to create the single COM domain + void CreateDefaultDomain(); + + //**************************************************************************************** + // Helper function to add a domain to the global list + void AddDomain(AppDomain* pDomain); + + void CreatePreallocatedExceptions(); + + void PreallocateSpecialObjects(); + + //**************************************************************************************** + // + static StackWalkAction CallersMethodCallback(CrawlFrame* pCrawlFrame, VOID* pClientData); + static StackWalkAction CallersMethodCallbackWithStackMark(CrawlFrame* pCrawlFrame, VOID* pClientData); + +#ifndef DACCESS_COMPILE + // This class is not to be created through normal allocation. + SystemDomain() + { + STANDARD_VM_CONTRACT; + + m_pDefaultDomain = NULL; + m_pDelayedUnloadList=NULL; + m_pDelayedUnloadListOfLoaderAllocators=NULL; + m_UnloadIsAsync = FALSE; + + m_GlobalAllocator.Init(this); + } +#endif + + PTR_PEAssembly m_pSystemFile; // Single assembly (here for quicker reference); + PTR_Assembly m_pSystemAssembly; // Single assembly (here for quicker reference); + PTR_AppDomain m_pDefaultDomain; // Default domain for COM+ classes exposed through IClassFactory. + + GlobalLoaderAllocator m_GlobalAllocator; + + + InlineSString<100> m_BaseLibrary; + +#ifdef FEATURE_VERSIONING + + InlineSString<100> m_SystemDirectory; + +#else + + LPCWSTR m_SystemDirectory; + +#endif + + LPWSTR m_pwDevpath; + DWORD m_dwDevpath; + BOOL m_fDevpath; // have we searched the environment + + // @TODO: CTS, we can keep the com modules in a single assembly or in different assemblies. + // We are currently using different assemblies but this is potentitially to slow... + + // Global domain that every one uses + SPTR_DECL(SystemDomain, m_pSystemDomain); + + AppDomain* m_pDelayedUnloadList; + BOOL m_UnloadIsAsync; + + LoaderAllocator * m_pDelayedUnloadListOfLoaderAllocators; + +#ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING + // This is what gets promoted for the whole GC heap. + static size_t m_totalSurvivedBytes; +#endif //FEATURE_APPDOMAIN_RESOURCE_MONITORING + + SVAL_DECL(ArrayListStatic, m_appDomainIndexList); +#ifndef DACCESS_COMPILE + static CrstStatic m_DelayedUnloadCrst; + static CrstStatic m_SystemDomainCrst; + + + static ArrayListStatic m_appDomainIdList; + + // only one ad can be unloaded at a time + static AppDomain* m_pAppDomainBeingUnloaded; + // need this so can determine AD being unloaded after it has been deleted + static ADIndex m_dwIndexOfAppDomainBeingUnloaded; + + // if had to spin off a separate thread to do the unload, this is the original thread. + // allows us to delay aborting it until it's the last one so that it can receive + // notification of an unload failure + static Thread *m_pAppDomainUnloadRequestingThread; + + // this is the thread doing the actual unload. He's allowed to enter the domain + // even if have started unloading. + static Thread *m_pAppDomainUnloadingThread; + + static GlobalStringLiteralMap *m_pGlobalStringLiteralMap; + + static ULONG s_dNumAppDomains; // Maintain a count of children app domains. + + static DWORD m_dwLowestFreeIndex; +#endif // DACCESS_COMPILE + +protected: + + // These flags let the correct native image of mscorlib to be loaded. + // This is important for hardbinding to it + + SVAL_DECL(BOOL, s_fForceDebug); + SVAL_DECL(BOOL, s_fForceProfiling); + SVAL_DECL(BOOL, s_fForceInstrument); + +public: + static void SetCompilationOverrides(BOOL fForceDebug, + BOOL fForceProfiling, + BOOL fForceInstrument); + + static void GetCompilationOverrides(BOOL * fForceDebug, + BOOL * fForceProfiling, + BOOL * fForceInstrument); +public: + //**************************************************************************************** + // + +#ifndef DACCESS_COMPILE +#ifdef _DEBUG +inline static BOOL IsUnderDomainLock() { LIMITED_METHOD_CONTRACT; return m_SystemDomainCrst.OwnedByCurrentThread();}; +#endif + + // This lock controls adding and removing domains from the system domain + class LockHolder : public CrstHolder + { + public: + LockHolder() + : CrstHolder(&m_SystemDomainCrst) + { + WRAPPER_NO_CONTRACT; + } + }; +#endif // DACCESS_COMPILE + +public: + DWORD GetTotalNumSizedRefHandles(); + +#ifdef DACCESS_COMPILE +public: + virtual void EnumMemoryRegions(CLRDataEnumMemoryFlags flags, + bool enumThis); +#endif + +}; // class SystemDomain + + +// +// an UnsafeAppDomainIterator is used to iterate over all existing domains +// +// The iteration is guaranteed to include all domains that exist at the +// start & end of the iteration. This iterator is considered unsafe because it does not +// reference count the various appdomains, and can only be used when the runtime is stopped, +// or external synchronization is used. (and therefore no other thread may cause the appdomain list to change.) +// +class UnsafeAppDomainIterator +{ + friend class SystemDomain; +public: + UnsafeAppDomainIterator(BOOL bOnlyActive) + { + m_bOnlyActive = bOnlyActive; + } + + void Init() + { + LIMITED_METHOD_CONTRACT; + SystemDomain* sysDomain = SystemDomain::System(); + if (sysDomain) + { + ArrayListStatic* list = &sysDomain->m_appDomainIndexList; + PREFIX_ASSUME(list != NULL); + m_i = list->Iterate(); + } + else + { + m_i.SetEmpty(); + } + + m_pCurrent = NULL; + } + + BOOL Next() + { + WRAPPER_NO_CONTRACT; + + while (m_i.Next()) + { + m_pCurrent = dac_cast(m_i.GetElement()); + if (m_pCurrent != NULL && + (m_bOnlyActive ? + m_pCurrent->IsActive() : m_pCurrent->IsValid())) + { + return TRUE; + } + } + + m_pCurrent = NULL; + return FALSE; + } + + AppDomain * GetDomain() + { + LIMITED_METHOD_DAC_CONTRACT; + + return m_pCurrent; + } + + private: + + ArrayList::Iterator m_i; + AppDomain * m_pCurrent; + BOOL m_bOnlyActive; +}; // class UnsafeAppDomainIterator + +// +// an AppDomainIterator is used to iterate over all existing domains. +// +// The iteration is guaranteed to include all domains that exist at the +// start & end of the iteration. Any domains added or deleted during +// iteration may or may not be included. The iterator also guarantees +// that the current iterated appdomain (GetDomain()) will not be deleted. +// + +class AppDomainIterator : public UnsafeAppDomainIterator +{ + friend class SystemDomain; + + public: + AppDomainIterator(BOOL bOnlyActive) : UnsafeAppDomainIterator(bOnlyActive) + { + WRAPPER_NO_CONTRACT; + Init(); + } + + ~AppDomainIterator() + { + WRAPPER_NO_CONTRACT; + +#ifndef DACCESS_COMPILE + if (GetDomain() != NULL) + { +#ifdef _DEBUG + GetDomain()->IteratorRelease(); +#endif + GetDomain()->Release(); + } +#endif + } + + BOOL Next() + { + WRAPPER_NO_CONTRACT; + +#ifndef DACCESS_COMPILE + if (GetDomain() != NULL) + { +#ifdef _DEBUG + GetDomain()->IteratorRelease(); +#endif + GetDomain()->Release(); + } + + SystemDomain::LockHolder lh; +#endif + + if (UnsafeAppDomainIterator::Next()) + { +#ifndef DACCESS_COMPILE + GetDomain()->AddRef(); +#ifdef _DEBUG + GetDomain()->IteratorAcquire(); +#endif +#endif + return TRUE; + } + + return FALSE; + } +}; // class AppDomainIterator + +typedef VPTR(class SharedDomain) PTR_SharedDomain; + +class SharedDomain : public BaseDomain +{ + VPTR_VTABLE_CLASS_AND_CTOR(SharedDomain, BaseDomain) + +public: + + static void Attach(); + static void Detach(); + + virtual BOOL IsSharedDomain() { LIMITED_METHOD_DAC_CONTRACT; return TRUE; } + virtual PTR_LoaderAllocator GetLoaderAllocator() { WRAPPER_NO_CONTRACT; return SystemDomain::GetGlobalLoaderAllocator(); } + + virtual PTR_AppDomain AsAppDomain() + { + LIMITED_METHOD_CONTRACT; + STATIC_CONTRACT_SO_TOLERANT; + _ASSERTE(!"Not an AppDomain"); + return NULL; + } + + static SharedDomain * GetDomain(); + + void Init(); + void Terminate(); + + // This will also set the tenured bit if and only if the add was successful, + // and will make sure that the bit appears atomically set to all readers that + // might be accessing the hash on another thread. + MethodTable * FindIndexClass(SIZE_T index); + +#ifdef FEATURE_LOADER_OPTIMIZATION + void AddShareableAssembly(Assembly * pAssembly); + + class SharedAssemblyIterator + { + PtrHashMap::PtrIterator i; + Assembly * m_pAssembly; + + public: + SharedAssemblyIterator() : + i(GetDomain() ? GetDomain()->m_assemblyMap.firstBucket() : NULL) + { LIMITED_METHOD_DAC_CONTRACT; } + + BOOL Next() + { + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + if (i.end()) + return FALSE; + + m_pAssembly = PTR_Assembly(dac_cast(i.GetValue())); + ++i; + return TRUE; + } + + Assembly * GetAssembly() + { + LIMITED_METHOD_DAC_CONTRACT; + + return m_pAssembly; + } + + private: + friend class SharedDomain; + }; + + Assembly * FindShareableAssembly(SharedAssemblyLocator * pLocator); + SIZE_T GetShareableAssemblyCount(); +#endif //FEATURE_LOADER_OPTIMIZATION + +private: + friend class SharedAssemblyIterator; + friend class SharedFileLockHolder; + friend class ClrDataAccess; + +#ifndef DACCESS_COMPILE + void *operator new(size_t size, void *pInPlace); + void operator delete(void *pMem); +#endif + + SPTR_DECL(SharedDomain, m_pSharedDomain); + +#ifdef FEATURE_LOADER_OPTIMIZATION + PEFileListLock m_FileCreateLock; + SIZE_T m_nextClassIndex; + PtrHashMap m_assemblyMap; +#endif + +public: +#ifdef DACCESS_COMPILE + virtual void EnumMemoryRegions(CLRDataEnumMemoryFlags flags, + bool enumThis); +#endif + +#ifdef FEATURE_LOADER_OPTIMIZATION + // Hash map comparison function` + static BOOL CompareSharedAssembly(UPTR u1, UPTR u2); +#endif +}; + +#ifdef FEATURE_LOADER_OPTIMIZATION +class SharedFileLockHolderBase : protected HolderBase +{ + protected: + PEFileListLock *m_pLock; + ListLockEntry *m_pLockElement; + + SharedFileLockHolderBase(PEFile *value) + : HolderBase(value) + { + LIMITED_METHOD_CONTRACT; + + m_pLock = NULL; + m_pLockElement = NULL; + } + +#ifndef DACCESS_COMPILE + void DoAcquire() + { + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_FAULT; + + PEFileListLockHolder lockHolder(m_pLock); + + m_pLockElement = m_pLock->FindFileLock(m_value); + if (m_pLockElement == NULL) + { + m_pLockElement = new ListLockEntry(m_pLock, m_value); + m_pLock->AddElement(m_pLockElement); + } + else + m_pLockElement->AddRef(); + + lockHolder.Release(); + + m_pLockElement->Enter(); + } + + void DoRelease() + { + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_FORBID_FAULT; + + m_pLockElement->Leave(); + m_pLockElement->Release(); + m_pLockElement = NULL; + } +#endif // DACCESS_COMPILE +}; + +class SharedFileLockHolder : public BaseHolder +{ + public: + DEBUG_NOINLINE SharedFileLockHolder(SharedDomain *pDomain, PEFile *pFile, BOOL Take = TRUE) + : BaseHolder(pFile, FALSE) + { + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_FAULT; + ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT; + + m_pLock = &pDomain->m_FileCreateLock; + if (Take) + Acquire(); + } +}; +#endif // FEATURE_LOADER_OPTIMIZATION + +inline BOOL BaseDomain::IsDefaultDomain() +{ + LIMITED_METHOD_DAC_CONTRACT; + return (SystemDomain::System()->DefaultDomain() == this); +} + +#include "comreflectioncache.inl" + +#if !defined(DACCESS_COMPILE) && !defined(CROSSGEN_COMPILE) +// holds an extra reference so needs special Extract() and should not have SuppressRelease() +// Holders/Wrappers have nonvirtual methods so cannot use them as the base class +template +class AppDomainCreationHolder +{ +private: + // disable the copy ctor + AppDomainCreationHolder(const AppDomainCreationHolder&) {} + +protected: + AppDomainType* m_pDomain; + BOOL m_bAcquired; + void ReleaseAppDomainDuringCreation() + { + CONTRACTL + { + NOTHROW; + WRAPPER(GC_TRIGGERS); + PRECONDITION(m_bAcquired); + PRECONDITION(CheckPointer(m_pDomain)); + } + CONTRACTL_END; + + if (m_pDomain->NotReadyForManagedCode()) + { + m_pDomain->Release(); + } + else + { + STRESS_LOG2 (LF_APPDOMAIN, LL_INFO100, "Unload domain during creation [%d] %p\n", m_pDomain->GetId().m_dwId, m_pDomain); + SystemDomain::MakeUnloadable(m_pDomain); +#ifdef _DEBUG + DWORD hostTestADUnload = g_pConfig->GetHostTestADUnload(); + m_pDomain->EnableADUnloadWorker(hostTestADUnload != 2?EEPolicy::ADU_Safe:EEPolicy::ADU_Rude); +#else + m_pDomain->EnableADUnloadWorker(EEPolicy::ADU_Safe); +#endif + } + }; + +public: + AppDomainCreationHolder() + { + m_pDomain=NULL; + m_bAcquired=FALSE; + }; + ~AppDomainCreationHolder() + { + if (m_bAcquired) + { + Release(); + } + }; + void Assign(AppDomainType* pDomain) + { + if(m_bAcquired) + Release(); + m_pDomain=pDomain; + if(m_pDomain) + { + AppDomain::RefTakerAcquire(m_pDomain); +#ifdef _DEBUG + m_pDomain->IncCreationCount(); +#endif // _DEBUG + } + m_bAcquired=TRUE; + }; + + void Release() + { + _ASSERTE(m_bAcquired); + if(m_pDomain) + { +#ifdef _DEBUG + m_pDomain->DecCreationCount(); +#endif // _DEBUG + if(!m_pDomain->IsDefaultDomain()) + ReleaseAppDomainDuringCreation(); + AppDomain::RefTakerRelease(m_pDomain); + }; + m_bAcquired=FALSE; + }; + + AppDomainType* Extract() + { + _ASSERTE(m_bAcquired); + if(m_pDomain) + { +#ifdef _DEBUG + m_pDomain->DecCreationCount(); +#endif // _DEBUG + AppDomain::RefTakerRelease(m_pDomain); + } + m_bAcquired=FALSE; + return m_pDomain; + }; + + AppDomainType* operator ->() + { + _ASSERTE(m_bAcquired); + return m_pDomain; + } + + operator AppDomainType*() + { + _ASSERTE(m_bAcquired); + return m_pDomain; + } + + void DoneCreating() + { + Extract(); + } +}; +#endif // !DACCESS_COMPILE && !CROSSGEN_COMPILE + +#endif diff --git a/src/vm/appdomain.inl b/src/vm/appdomain.inl new file mode 100644 index 0000000000..210334c78e --- /dev/null +++ b/src/vm/appdomain.inl @@ -0,0 +1,323 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +/*============================================================ +** +** Header: AppDomain.i +** + +** +** Purpose: Implements AppDomain (loader domain) architecture +** inline functions +** +** +===========================================================*/ +#ifndef _APPDOMAIN_I +#define _APPDOMAIN_I + +#ifndef DACCESS_COMPILE + +#include "appdomain.hpp" + +inline void AppDomain::SetUnloadInProgress(AppDomain *pThis) +{ + WRAPPER_NO_CONTRACT; + + SystemDomain::System()->SetUnloadInProgress(pThis); +} + +inline void AppDomain::SetUnloadComplete(AppDomain *pThis) +{ + GCX_COOP(); + + SystemDomain::System()->SetUnloadComplete(); +} + +inline void AppDomain::EnterContext(Thread* pThread, Context* pCtx,ContextTransitionFrame *pFrame) +{ + CONTRACTL + { + GC_NOTRIGGER; + MODE_COOPERATIVE; + PRECONDITION(CheckPointer(pThread)); + PRECONDITION(CheckPointer(pCtx)); + PRECONDITION(CheckPointer(pFrame)); + PRECONDITION(pCtx->GetDomain()==this); + } + CONTRACTL_END; + pThread->EnterContextRestricted(pCtx,pFrame); +}; + + +inline AppDomainFromIDHolder::~AppDomainFromIDHolder() +{ + WRAPPER_NO_CONTRACT; +#ifdef _DEBUG + if(m_bAcquired) + Release(); +#endif +} + +inline void AppDomainFromIDHolder::Release() +{ + //do not use real contract here! + WRAPPER_NO_CONTRACT; +#ifdef _DEBUG + if(m_bAcquired) + { + if (m_type==SyncType_GC) +#ifdef ENABLE_CONTRACTS_IMPL + { + if (GetThread()) + { + STRESS_LOG1(LF_APPDOMAIN, LL_INFO10000, "AppDomainFromIDHolder::Assign is allowing GC - %08x",this); + GetThread()->EndForbidGC(); + } + else + { + if (!IsGCThread()) + { + _ASSERTE(!"Should not be called from a non GC thread"); + } + } + } +#else + m_pDomain=NULL; +#endif + else + if (m_type==SyncType_ADLock) + SystemDomain::m_SystemDomainCrst.SetCantLeave(FALSE); + else + { + _ASSERTE(!"Unknown type"); + } + m_pDomain=NULL; + m_bAcquired=FALSE; + } +#endif +} + +inline void AppDomainFromIDHolder::Assign(ADID id, BOOL bUnsafePoint) +{ + //do not use real contract here! + WRAPPER_NO_CONTRACT; + TESTHOOKCALL(AppDomainCanBeUnloaded(id.m_dwId, bUnsafePoint)); +#ifdef _DEBUG + m_bChecked=FALSE; + if (m_type==SyncType_GC) + { +#ifdef ENABLE_CONTRACTS_IMPL + if (GetThread()) + { + _ASSERTE(GetThread()->PreemptiveGCDisabled()); + STRESS_LOG1(LF_APPDOMAIN, LL_INFO10000, "AppDomainFromIDHolder::Assign is forbidding GC - %08x",this); + GetThread()->BeginForbidGC(__FILE__, __LINE__); + } + else + { + if (!IsGCThread()) + { + _ASSERTE(!"Should not be called from a non GC thread"); + } + } +#endif + } + else + if (m_type==SyncType_ADLock) + { + _ASSERTE(SystemDomain::m_SystemDomainCrst.OwnedByCurrentThread()); + SystemDomain::m_SystemDomainCrst.SetCantLeave(TRUE); + } + else + { + _ASSERT(!"NI"); + } + + m_bAcquired=TRUE; + #endif + m_pDomain=SystemDomain::GetAppDomainAtId(id); + +} + + + +inline void AppDomainFromIDHolder::ThrowIfUnloaded() +{ + STATIC_CONTRACT_THROWS; + if (IsUnloaded()) + { + COMPlusThrow(kAppDomainUnloadedException); + } +#ifdef _DEBUG + m_bChecked=TRUE; +#endif +} + +inline AppDomain* AppDomainFromIDHolder::operator ->() +{ + LIMITED_METHOD_CONTRACT; + _ASSERTE(m_bChecked && m_bAcquired); + return m_pDomain; +} + +inline DomainAssembly* AppDomain::FindDomainAssembly(Assembly* assembly) +{ + CONTRACTL + { + GC_NOTRIGGER; + MODE_COOPERATIVE; + PRECONDITION(CheckPointer(assembly)); + } + CONTRACTL_END; + return assembly->FindDomainAssembly(this); +}; + +inline BOOL AppDomain::IsRunningIn(Thread* pThread) +{ + WRAPPER_NO_CONTRACT; + if (IsDefaultDomain()) + return TRUE; + return pThread->IsRunningIn(this, NULL)!=NULL; +} + + + +inline void AppDomain::AddMemoryPressure() +{ + STANDARD_VM_CONTRACT; + m_MemoryPressure=EstimateSize(); + GCInterface::AddMemoryPressure(m_MemoryPressure); +} + +inline void AppDomain::RemoveMemoryPressure() +{ + WRAPPER_NO_CONTRACT; + + GCInterface::RemoveMemoryPressure(m_MemoryPressure); +} + +#endif // DACCESS_COMPILE + +inline void AppDomain::SetAppDomainManagerInfo(LPCWSTR szAssemblyName, LPCWSTR szTypeName, EInitializeNewDomainFlags dwInitializeDomainFlags) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + m_AppDomainManagerAssembly=szAssemblyName; + m_AppDomainManagerType=szTypeName; + m_dwAppDomainManagerInitializeDomainFlags = dwInitializeDomainFlags; +} + +inline BOOL AppDomain::HasAppDomainManagerInfo() +{ + WRAPPER_NO_CONTRACT; + return !m_AppDomainManagerAssembly.IsEmpty() && !m_AppDomainManagerType.IsEmpty(); +} + +inline LPCWSTR AppDomain::GetAppDomainManagerAsm() +{ + WRAPPER_NO_CONTRACT; + return m_AppDomainManagerAssembly; +} + + +inline LPCWSTR AppDomain::GetAppDomainManagerType() +{ + WRAPPER_NO_CONTRACT; + return m_AppDomainManagerType; +} + +#ifndef FEATURE_CORECLR +inline BOOL AppDomain::AppDomainManagerSetFromConfig() +{ + WRAPPER_NO_CONTRACT; + return m_fAppDomainManagerSetInConfig; +} +#endif // !FEATURE_CORECLR + +inline EInitializeNewDomainFlags AppDomain::GetAppDomainManagerInitializeNewDomainFlags() +{ + LIMITED_METHOD_CONTRACT; + return m_dwAppDomainManagerInitializeDomainFlags; +} + +#ifdef FEATURE_CORECLR +inline AppDomain::PathIterator AppDomain::IterateNativeDllSearchDirectories() +{ + WRAPPER_NO_CONTRACT; + PathIterator i; + i.m_i = m_NativeDllSearchDirectories.Iterate(); + return i; +} + +inline BOOL AppDomain::HasNativeDllSearchDirectories() +{ + WRAPPER_NO_CONTRACT; + return m_NativeDllSearchDirectories.GetCount() !=0; +} + +#endif // FEATURE_CORECLR + +inline BOOL AppDomain::CanReversePInvokeEnter() +{ + LIMITED_METHOD_CONTRACT; + return m_ReversePInvokeCanEnter; +} + +inline void AppDomain::SetReversePInvokeCannotEnter() +{ + LIMITED_METHOD_CONTRACT; + m_ReversePInvokeCanEnter=FALSE; +} + +inline bool AppDomain::MustForceTrivialWaitOperations() +{ + LIMITED_METHOD_CONTRACT; + return m_ForceTrivialWaitOperations; +} + +inline void AppDomain::SetForceTrivialWaitOperations() +{ + LIMITED_METHOD_CONTRACT; + m_ForceTrivialWaitOperations = true; +} + +inline PTR_LoaderHeap AppDomain::GetHighFrequencyHeap() +{ + WRAPPER_NO_CONTRACT; + return GetLoaderAllocator()->GetHighFrequencyHeap(); +} + +inline PTR_LoaderHeap AppDomain::GetLowFrequencyHeap() +{ + WRAPPER_NO_CONTRACT; + return GetLoaderAllocator()->GetLowFrequencyHeap(); +} + +inline PTR_LoaderHeap AppDomain::GetStubHeap() +{ + WRAPPER_NO_CONTRACT; + return GetLoaderAllocator()->GetStubHeap(); +} + +inline PTR_LoaderAllocator AppDomain::GetLoaderAllocator() +{ + WRAPPER_NO_CONTRACT; + return PTR_LoaderAllocator(PTR_HOST_MEMBER_TADDR(AppDomain,this,m_LoaderAllocator)); +} + +/* static */ +inline DWORD DomainLocalModule::DynamicEntry::GetOffsetOfDataBlob() +{ + LIMITED_METHOD_CONTRACT; + _ASSERTE(DWORD(offsetof(NormalDynamicEntry, m_pDataBlob)) == offsetof(NormalDynamicEntry, m_pDataBlob)); + return (DWORD)offsetof(NormalDynamicEntry, m_pDataBlob); +} + + +#endif // _APPDOMAIN_I + diff --git a/src/vm/appdomainconfigfactory.hpp b/src/vm/appdomainconfigfactory.hpp new file mode 100644 index 0000000000..b443797c0d --- /dev/null +++ b/src/vm/appdomainconfigfactory.hpp @@ -0,0 +1,240 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + + +#ifndef APPDOMAINCONFIGFACTORY_H +#define APPDOMAINCONFIGFACTORY_H + +#include +#include +#include "unknwn.h" +#include "../xmlparser/_reference.h" +#include "../xmlparser/_unknown.h" + +#include "appdomain.hpp" + +#define ISWHITE(ch) ((ch) >= 0x09 && (ch) <= 0x0D || (ch) == 0x20) + +#define CONST_STRING_AND_LEN(str) str, NumItems(str)-1 + + +extern int EEXMLStringCompare(const WCHAR *pStr1, + DWORD cchStr1, + const WCHAR *pStr2, + DWORD cchStr2); + + +enum APPDOMAINPARSESTATE +{ + APPDOMAINPARSESTATE_INITIALIZED, + APPDOMAINPARSESTATE_RUNTIME, + APPDOMAINPARSESTATE_PREFERCOMINSTEADOFREMOTING, + APPDOMAINPARSESTATE_ENABLED, + APPDOMAINPARSESTATE_LEGACYMODE +}; + + + +class AppDomainConfigFactory : public _unknown +{ + +public: + AppDomainConfigFactory() : m_dwDepth(0), comorRemotingFlag(COMorRemoting_NotInitialized), m_appdomainParseState(APPDOMAINPARSESTATE_INITIALIZED) + { + LIMITED_METHOD_CONTRACT; + } + + ~AppDomainConfigFactory() + { + LIMITED_METHOD_CONTRACT; + } + + HRESULT STDMETHODCALLTYPE NotifyEvent( + /* [in] */ IXMLNodeSource __RPC_FAR *pSource, + /* [in] */ XML_NODEFACTORY_EVENT iEvt) + { + LIMITED_METHOD_CONTRACT; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE BeginChildren( + /* [in] */ IXMLNodeSource __RPC_FAR *pSource, + /* [in] */ XML_NODE_INFO* __RPC_FAR pNodeInfo) + { + LIMITED_METHOD_CONTRACT; + + m_dwDepth++; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE EndChildren( + /* [in] */ IXMLNodeSource __RPC_FAR *pSource, + /* [in] */ BOOL fEmptyNode, + /* [in] */ XML_NODE_INFO* __RPC_FAR pNodeInfo) + { + LIMITED_METHOD_CONTRACT; + + if (!fEmptyNode) + m_dwDepth--; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE Error( + /* [in] */ IXMLNodeSource __RPC_FAR *pSource, + /* [in] */ HRESULT hrErrorCode, + /* [in] */ USHORT cNumRecs, + /* [in] */ XML_NODE_INFO* __RPC_FAR * __RPC_FAR apNodeInfo) + { + LIMITED_METHOD_CONTRACT; + /* + UNUSED(pSource); + UNUSED(hrErrorCode); + UNUSED(cNumRecs); + UNUSED(apNodeInfo); + */ + return hrErrorCode; + } + + HRESULT STDMETHODCALLTYPE CreateNode( + /* [in] */ IXMLNodeSource __RPC_FAR *pSource, + /* [in] */ PVOID pNodeParent, + /* [in] */ USHORT cNumRecs, + /* [in] */ XML_NODE_INFO* __RPC_FAR * __RPC_FAR apNodeInfo) + { + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END; + + if(m_dwDepth > 2) + { + return S_OK; + } + + HRESULT hr = S_OK; + DWORD dwStringSize = 0; + WCHAR* pszString = NULL; + DWORD i; + BOOL fRuntimeKey = FALSE; + BOOL fVersion = FALSE; + + for( i = 0; i < cNumRecs; i++) { + + if(apNodeInfo[i]->dwType == XML_ELEMENT || + apNodeInfo[i]->dwType == XML_ATTRIBUTE || + apNodeInfo[i]->dwType == XML_PCDATA) { + + dwStringSize = apNodeInfo[i]->ulLen; + pszString = (WCHAR*) apNodeInfo[i]->pwcText; + // Trim the value + + // we should never decrement lgth if it's 0, because it's unsigned + + for(;*pszString && ISWHITE(*pszString) && dwStringSize>0; pszString++, dwStringSize--); + while( dwStringSize > 0 && ISWHITE(pszString[dwStringSize-1])) + dwStringSize--; + + if (m_appdomainParseState == APPDOMAINPARSESTATE_INITIALIZED) + { + //look forward to + if (m_dwDepth == 1 && + apNodeInfo[i]->dwType == XML_ELEMENT && + EEXMLStringCompare(pszString, dwStringSize, CONST_STRING_AND_LEN(W("runtime"))) == 0) + { + m_appdomainParseState = APPDOMAINPARSESTATE_RUNTIME; + } + return S_OK; + } + else if (m_appdomainParseState == APPDOMAINPARSESTATE_RUNTIME) + { + // look forward to + if (m_dwDepth == 2 && + apNodeInfo[i]->dwType == XML_ELEMENT && + EEXMLStringCompare(pszString, dwStringSize, CONST_STRING_AND_LEN(W("PreferComInsteadOfManagedRemoting"))) == 0) + { + m_appdomainParseState = APPDOMAINPARSESTATE_PREFERCOMINSTEADOFREMOTING; + continue; + } + // if we ended parsing , we abort it + if (m_dwDepth <= 1) + pSource->Abort(NULL); + return S_OK; + } + else if (m_appdomainParseState == APPDOMAINPARSESTATE_PREFERCOMINSTEADOFREMOTING) + { + // require enabled="true"/> or legacyMode="true"/> + if (m_dwDepth == 2 && + apNodeInfo[i]->dwType == XML_ATTRIBUTE) + { + if (EEXMLStringCompare(pszString, dwStringSize, CONST_STRING_AND_LEN(W("enabled"))) == 0) + { + m_appdomainParseState = APPDOMAINPARSESTATE_ENABLED; + } + if (EEXMLStringCompare(pszString, dwStringSize, CONST_STRING_AND_LEN(W("legacyMode"))) == 0) + { + m_appdomainParseState = APPDOMAINPARSESTATE_LEGACYMODE; + } + } + + // ignore unrecognized attributes (forward compat) + continue; + } + else if (m_appdomainParseState == APPDOMAINPARSESTATE_ENABLED || m_appdomainParseState == APPDOMAINPARSESTATE_LEGACYMODE) + { + // require "true" /> or "false" /> + if (m_dwDepth == 2 && + apNodeInfo[i]->dwType == XML_PCDATA) + { + if (EEXMLStringCompare(pszString, dwStringSize, CONST_STRING_AND_LEN(W("true"))) == 0) + { + if (m_appdomainParseState == APPDOMAINPARSESTATE_LEGACYMODE) + { + // LegacyMode does not override the "master switch" + if (comorRemotingFlag != COMorRemoting_COM) + comorRemotingFlag = COMorRemoting_LegacyMode; + } + else + { + comorRemotingFlag = COMorRemoting_COM; + } + } + else if (EEXMLStringCompare(pszString, dwStringSize, CONST_STRING_AND_LEN(W("false"))) == 0) + { + if (m_appdomainParseState == APPDOMAINPARSESTATE_ENABLED) + { + // we do report that the "master switch" is explictly false + if (comorRemotingFlag == COMorRemoting_NotInitialized) + comorRemotingFlag = COMorRemoting_Remoting; + } + } + + m_appdomainParseState = APPDOMAINPARSESTATE_PREFERCOMINSTEADOFREMOTING; + continue; + } + pSource->Abort(NULL); + return S_OK; + } + } + } + return hr; + } + + COMorRemotingFlag GetCOMorRemotingFlag() + { + LIMITED_METHOD_CONTRACT; + return comorRemotingFlag; + } + +private: + DWORD m_dwDepth; + COMorRemotingFlag comorRemotingFlag; + APPDOMAINPARSESTATE m_appdomainParseState; + +}; + +#endif APPDOMAINCONFIGFACTORY_H diff --git a/src/vm/appdomainhelper.cpp b/src/vm/appdomainhelper.cpp new file mode 100644 index 0000000000..2c0531648f --- /dev/null +++ b/src/vm/appdomainhelper.cpp @@ -0,0 +1,546 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + + +// + +#include "common.h" + +#ifdef FEATURE_REMOTING + +#include "appdomainhelper.h" +#include "appdomain.inl" + +void AppDomainHelper::CopyEncodingToByteArray(IN PBYTE pbData, + IN DWORD cbData, + OUT OBJECTREF* pArray) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(cbData==0 || pbData!=NULL); + PRECONDITION(CheckPointer(pArray)); + } + CONTRACTL_END; + PREFIX_ASSUME(pArray != NULL); + + U1ARRAYREF pObj; + + if(cbData) { + pObj = (U1ARRAYREF)AllocatePrimitiveArray(ELEMENT_TYPE_U1,cbData); + memcpyNoGCRefs(pObj->m_Array, pbData, cbData); + *pArray = (OBJECTREF) pObj; + } else + *pArray = NULL; + + VALIDATEOBJECTREF(*pArray); +} + + +void AppDomainHelper::CopyByteArrayToEncoding(IN U1ARRAYREF* pArray, + OUT PBYTE* ppbData, + OUT DWORD* pcbData) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_COOPERATIVE; + PRECONDITION(pArray!=NULL); + PRECONDITION(ppbData!=NULL); + PRECONDITION(pcbData!=NULL); + } + CONTRACTL_END; + + VALIDATEOBJECTREF(*pArray); + + if (*pArray == NULL) { + *ppbData = NULL; + *pcbData = 0; + return; + } + + DWORD size = (*pArray)->GetNumComponents(); + if(size) { + *ppbData = new BYTE[size]; + *pcbData = size; + + CopyMemory(*ppbData, (*pArray)->GetDirectPointerToNonObjectElements(), size); + } +} + + +struct MarshalObjectArgs : public CtxTransitionBaseArgs +{ + OBJECTREF* orObject; + U1ARRAYREF* porBlob; +}; + +void MarshalObjectADCallback(MarshalObjectArgs * args) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + MethodDescCallSite marshalObject(METHOD__APP_DOMAIN__MARSHAL_OBJECT); + + ARG_SLOT argsCall[] = { + ObjToArgSlot(*(args->orObject)) + }; + + *(args->porBlob) = (U1ARRAYREF) marshalObject.Call_RetOBJECTREF(argsCall); +} + + +// Marshal a single object into a serialized blob. +void AppDomainHelper::MarshalObject(ADID appDomain, + IN OBJECTREF *orObject, // Object must be GC protected + OUT U1ARRAYREF *porBlob) +{ + + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(orObject!=NULL); + PRECONDITION(porBlob!=NULL); + PRECONDITION(IsProtectedByGCFrame(orObject)); + } + CONTRACTL_END; + + VALIDATEOBJECTREF(*orObject); + + MarshalObjectArgs args; + args.orObject = orObject; + args.porBlob = porBlob; + + MakeCallWithPossibleAppDomainTransition(appDomain, (FPAPPDOMAINCALLBACK) MarshalObjectADCallback, &args); + + VALIDATEOBJECTREF(*porBlob); + +} + +void AppDomainHelper::MarshalObject(IN OBJECTREF *orObject, // Object must be GC protected + OUT U1ARRAYREF *porBlob) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(orObject!=NULL); + PRECONDITION(porBlob!=NULL); + PRECONDITION(IsProtectedByGCFrame(orObject)); + } + CONTRACTL_END; + + VALIDATEOBJECTREF(*orObject); + + MethodDescCallSite marshalObject(METHOD__APP_DOMAIN__MARSHAL_OBJECT); + + ARG_SLOT argsCall[] = { + ObjToArgSlot(*orObject) + }; + + *porBlob = (U1ARRAYREF) marshalObject.Call_RetOBJECTREF(argsCall); + + VALIDATEOBJECTREF(*porBlob); +} + +// Marshal a single object into a serialized blob. +void AppDomainHelper::MarshalObject(IN AppDomain *pDomain, + IN OBJECTREF *orObject, // Object must be GC protected + OUT BYTE **ppbBlob, + OUT DWORD *pcbBlob) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(pDomain!=NULL); + PRECONDITION(orObject!=NULL); + PRECONDITION(ppbBlob!=NULL); + PRECONDITION(pcbBlob!=NULL); + PRECONDITION(IsProtectedByGCFrame(orObject)); + } + CONTRACTL_END; + + VALIDATEOBJECTREF(*orObject); + + U1ARRAYREF orBlob = NULL; + + GCPROTECT_BEGIN(orBlob); + + MethodDescCallSite marshalObject(METHOD__APP_DOMAIN__MARSHAL_OBJECT); + + ENTER_DOMAIN_PTR(pDomain,ADV_RUNNINGIN) + { + ARG_SLOT args[] = + { + ObjToArgSlot(*orObject) + }; + + orBlob = (U1ARRAYREF) marshalObject.Call_RetOBJECTREF(args); + } + END_DOMAIN_TRANSITION; + + if (orBlob != NULL) + CopyByteArrayToEncoding(&orBlob, + ppbBlob, + pcbBlob); + GCPROTECT_END(); +} + +// Marshal two objects into serialized blobs. +void AppDomainHelper::MarshalObjects(IN AppDomain *pDomain, + IN OBJECTREF *orObject1, + IN OBJECTREF *orObject2, + OUT BYTE **ppbBlob1, + OUT DWORD *pcbBlob1, + OUT BYTE **ppbBlob2, + OUT DWORD *pcbBlob2) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(pDomain!=NULL); + PRECONDITION(orObject1!=NULL); + PRECONDITION(ppbBlob1!=NULL); + PRECONDITION(pcbBlob1!=NULL); + PRECONDITION(orObject2!=NULL); + PRECONDITION(ppbBlob2!=NULL); + PRECONDITION(pcbBlob2!=NULL); + PRECONDITION(IsProtectedByGCFrame(orObject1)); + PRECONDITION(IsProtectedByGCFrame(orObject2)); + } + CONTRACTL_END; + + VALIDATEOBJECTREF(*orObject1); + VALIDATEOBJECTREF(*orObject2); + + struct _gc { + U1ARRAYREF orBlob1; + U1ARRAYREF orBlob2; + } gc; + ZeroMemory(&gc, sizeof(gc)); + + GCPROTECT_BEGIN(gc); + + MethodDescCallSite marshalObjects(METHOD__APP_DOMAIN__MARSHAL_OBJECTS); + + ENTER_DOMAIN_PTR(pDomain,ADV_RUNNINGIN) + { + ARG_SLOT args[] = + { + ObjToArgSlot(*orObject1), + ObjToArgSlot(*orObject2), + PtrToArgSlot(&gc.orBlob2), + }; + + gc.orBlob1 = (U1ARRAYREF) marshalObjects.Call_RetOBJECTREF(args); + } + END_DOMAIN_TRANSITION; + + if (gc.orBlob1 != NULL) + { + CopyByteArrayToEncoding(&gc.orBlob1, + ppbBlob1, + pcbBlob1); + } + + if (gc.orBlob2 != NULL) + { + CopyByteArrayToEncoding(&gc.orBlob2, + ppbBlob2, + pcbBlob2); + } + + GCPROTECT_END(); +} + +// Unmarshal a single object from a serialized blob. +// Callers must GC protect both porBlob and porObject. +void AppDomainHelper::UnmarshalObject(IN AppDomain *pDomain, + IN U1ARRAYREF *porBlob, // Object must be GC protected + OUT OBJECTREF *porObject) // Object must be GC protected +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(pDomain!=NULL); + PRECONDITION(porBlob!=NULL); + PRECONDITION(porObject!=NULL); + PRECONDITION(IsProtectedByGCFrame(porBlob)); + PRECONDITION(IsProtectedByGCFrame(porObject)); + } + CONTRACTL_END; + + VALIDATEOBJECTREF(*porBlob); + + MethodDescCallSite unmarshalObject(METHOD__APP_DOMAIN__UNMARSHAL_OBJECT); + + ENTER_DOMAIN_PTR(pDomain,ADV_RUNNINGIN) + { + ARG_SLOT args[] = + { + ObjToArgSlot(*porBlob) + }; + + *porObject = unmarshalObject.Call_RetOBJECTREF(args); + } + END_DOMAIN_TRANSITION; + + VALIDATEOBJECTREF(*porObject); +} + +// Unmarshal a single object from a serialized blob. +void AppDomainHelper::UnmarshalObject(IN AppDomain *pDomain, + IN BYTE *pbBlob, + IN DWORD cbBlob, + OUT OBJECTREF *porObject) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(pDomain!=NULL); + PRECONDITION(porObject!=NULL); + PRECONDITION(IsProtectedByGCFrame(porObject)); + } + CONTRACTL_END; + + OBJECTREF orBlob = NULL; + + MethodDescCallSite unmarshalObject(METHOD__APP_DOMAIN__UNMARSHAL_OBJECT); + + ENTER_DOMAIN_PTR(pDomain,ADV_RUNNINGIN) + { + GCPROTECT_BEGIN(orBlob); + + AppDomainHelper::CopyEncodingToByteArray(pbBlob, + cbBlob, + &orBlob); + + ARG_SLOT args[] = + { + ObjToArgSlot(orBlob) + }; + + *porObject = unmarshalObject.Call_RetOBJECTREF(args); + + GCPROTECT_END(); + } + END_DOMAIN_TRANSITION; + + VALIDATEOBJECTREF(*porObject); +} + +// Unmarshal two objects from serialized blobs. +void AppDomainHelper::UnmarshalObjects(IN AppDomain *pDomain, + IN BYTE *pbBlob1, + IN DWORD cbBlob1, + IN BYTE *pbBlob2, + IN DWORD cbBlob2, + OUT OBJECTREF *porObject1, + OUT OBJECTREF *porObject2) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(pDomain!=NULL); + PRECONDITION(porObject1!=NULL); + PRECONDITION(porObject2!=NULL); + PRECONDITION(IsProtectedByGCFrame(porObject1)); + PRECONDITION(IsProtectedByGCFrame(porObject2)); + } + CONTRACTL_END; + + MethodDescCallSite unmarshalObjects(METHOD__APP_DOMAIN__UNMARSHAL_OBJECTS); + + struct _gc { + OBJECTREF orBlob1; + OBJECTREF orBlob2; + OBJECTREF orObject2; + } gc; + ZeroMemory(&gc, sizeof(gc)); + + ENTER_DOMAIN_PTR(pDomain,ADV_RUNNINGIN) + { + + GCPROTECT_BEGIN(gc); + + AppDomainHelper::CopyEncodingToByteArray(pbBlob1, + cbBlob1, + &gc.orBlob1); + + AppDomainHelper::CopyEncodingToByteArray(pbBlob2, + cbBlob2, + &gc.orBlob2); + + ARG_SLOT args[] = + { + ObjToArgSlot(gc.orBlob1), + ObjToArgSlot(gc.orBlob2), + PtrToArgSlot(&gc.orObject2), + }; + + *porObject1 = unmarshalObjects.Call_RetOBJECTREF(args); + *porObject2 = gc.orObject2; + + GCPROTECT_END(); + } + END_DOMAIN_TRANSITION; + + VALIDATEOBJECTREF(*porObject1); + VALIDATEOBJECTREF(*porObject2); +} + +// Copy an object from the given appdomain into the current appdomain. +OBJECTREF AppDomainHelper::CrossContextCopyFrom(IN ADID dwDomainId, + IN OBJECTREF *orObject) // Object must be GC protected +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(orObject!=NULL); + PRECONDITION(IsProtectedByGCFrame(orObject)); + } + CONTRACTL_END; + + struct _gc + { + U1ARRAYREF orBlob; + OBJECTREF pResult; + } gc; + ZeroMemory(&gc, sizeof(gc)); + + GCPROTECT_BEGIN(gc); + AppDomainHelper::MarshalObject(dwDomainId, orObject, &gc.orBlob); + AppDomainHelper::UnmarshalObject(GetAppDomain(), &gc.orBlob, &gc.pResult); + GCPROTECT_END(); + VALIDATEOBJECTREF(gc.pResult); + return gc.pResult; +} + +// Copy an object from the given appdomain into the current appdomain. +OBJECTREF AppDomainHelper::CrossContextCopyTo(IN ADID dwDomainId, + IN OBJECTREF *orObject) // Object must be GC protected +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(orObject!=NULL); + PRECONDITION(IsProtectedByGCFrame(orObject)); + } + CONTRACTL_END; + + + struct _gc + { + U1ARRAYREF orBlob; + OBJECTREF pResult; + } gc; + ZeroMemory(&gc, sizeof(gc)); + + GCPROTECT_BEGIN(gc); + AppDomainHelper::MarshalObject(orObject, &gc.orBlob); + ENTER_DOMAIN_ID(dwDomainId); + AppDomainHelper::UnmarshalObject(GetAppDomain(),&gc.orBlob, &gc.pResult); + END_DOMAIN_TRANSITION; + GCPROTECT_END(); + VALIDATEOBJECTREF(gc.pResult); + return gc.pResult; + +} + +// Copy an object from the given appdomain into the current appdomain. +OBJECTREF AppDomainHelper::CrossContextCopyFrom(IN AppDomain *pDomain, + IN OBJECTREF *orObject) // Object must be GC protected +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(orObject!=NULL); + PRECONDITION(IsProtectedByGCFrame(orObject)); + PRECONDITION(pDomain!=NULL); + PRECONDITION(pDomain != GetAppDomain()); + } + CONTRACTL_END; + + VALIDATEOBJECTREF(*orObject); + + struct _gc { + U1ARRAYREF orBlob; + OBJECTREF result; + } gc; + ZeroMemory(&gc, sizeof(gc)); + + GCPROTECT_BEGIN(gc); + ENTER_DOMAIN_PTR(pDomain, ADV_RUNNINGIN); + AppDomainHelper::MarshalObject(orObject, &gc.orBlob); + END_DOMAIN_TRANSITION; + AppDomainHelper::UnmarshalObject(GetAppDomain(),&gc.orBlob, &gc.result); + GCPROTECT_END(); + + VALIDATEOBJECTREF(gc.result); + + return gc.result; +} + +// Copy an object to the given appdomain from the current appdomain. +OBJECTREF AppDomainHelper::CrossContextCopyTo(IN AppDomain *pDomain, + IN OBJECTREF *orObject) // Object must be GC protected +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(orObject!=NULL); + PRECONDITION(IsProtectedByGCFrame(orObject)); + PRECONDITION(pDomain!=NULL); + PRECONDITION(pDomain != GetAppDomain()); + } + CONTRACTL_END; + + VALIDATEOBJECTREF(*orObject); + + struct _gc { + U1ARRAYREF orBlob; + OBJECTREF result; + } gc; + ZeroMemory(&gc, sizeof(gc)); + + GCPROTECT_BEGIN(gc); + AppDomainHelper::MarshalObject(orObject, &gc.orBlob); + AppDomainHelper::UnmarshalObject(pDomain, &gc.orBlob, &gc.result); + GCPROTECT_END(); + + VALIDATEOBJECTREF(gc.result); + + return gc.result; +} + +#endif // FEATURE_REMOTING + diff --git a/src/vm/appdomainhelper.h b/src/vm/appdomainhelper.h new file mode 100644 index 0000000000..e331555292 --- /dev/null +++ b/src/vm/appdomainhelper.h @@ -0,0 +1,371 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + + +#ifndef _APPDOMAIN_HELPER_H_ +#define _APPDOMAIN_HELPER_H_ + +#ifndef FEATURE_REMOTING +#error FEATURE_REMOTING is not set, please do not include appdomainhelper.h +#endif + +// Marshal a single object into a serialized blob. +// +// + +class AppDomainHelper { + + friend class MarshalCache; + + // A pair of helper to move serialization info between managed byte-array and + // unmanaged blob. + static void AppDomainHelper::CopyEncodingToByteArray(IN PBYTE pbData, + IN DWORD cbData, + OUT OBJECTREF* pArray); + + static void AppDomainHelper::CopyByteArrayToEncoding(IN U1ARRAYREF* pArray, + OUT PBYTE* ppbData, + OUT DWORD* pcbData); + +public: + // Marshal a single object into a serialized blob. + static void AppDomainHelper::MarshalObject(IN OBJECTREF *orObject, + OUT U1ARRAYREF *porBlob); + + static void AppDomainHelper::MarshalObject(IN ADID pDomain, + IN OBJECTREF *orObject, + OUT U1ARRAYREF *porBlob); + // Marshal one object into a seraialized blob. + static void AppDomainHelper::MarshalObject(IN AppDomain *pDomain, + IN OBJECTREF *orObject, + OUT BYTE **ppbBlob, + OUT DWORD *pcbBlob); + + // Marshal two objects into serialized blobs. + static void AppDomainHelper::MarshalObjects(IN AppDomain *pDomain, + IN OBJECTREF *orObject1, + IN OBJECTREF *orObject2, + OUT BYTE **ppbBlob1, + OUT DWORD *pcbBlob1, + OUT BYTE **ppbBlob2, + OUT DWORD *pcbBlob2); + + // Unmarshal a single object from a serialized blob. + static void AppDomainHelper::UnmarshalObject(IN AppDomain *pDomain, + IN U1ARRAYREF *porBlob, + OUT OBJECTREF *porObject); + + // Unmarshal a single object from a serialized blob. + static void AppDomainHelper::UnmarshalObject(IN AppDomain *pDomain, + IN BYTE *pbBlob, + IN DWORD cbBlob, + OUT OBJECTREF *porObject); + + // Unmarshal two objects from serialized blobs. + static void AppDomainHelper::UnmarshalObjects(IN AppDomain *pDomain, + IN BYTE *pbBlob1, + IN DWORD cbBlob1, + IN BYTE *pbBlob2, + IN DWORD cbBlob2, + OUT OBJECTREF *porObject1, + OUT OBJECTREF *porObject2); + + // Copy an object from the given appdomain into the current appdomain. + static OBJECTREF AppDomainHelper::CrossContextCopyFrom(IN AppDomain *pAppDomain, + IN OBJECTREF *orObject); + // Copy an object to the given appdomain from the current appdomain. + static OBJECTREF AppDomainHelper::CrossContextCopyTo(IN AppDomain *pAppDomain, + IN OBJECTREF *orObject); + // Copy an object from the given appdomain into the current appdomain. + static OBJECTREF AppDomainHelper::CrossContextCopyFrom(IN ADID dwDomainId, + IN OBJECTREF *orObject); + // Copy an object to the given appdomain from the current appdomain. + static OBJECTREF AppDomainHelper::CrossContextCopyTo(IN ADID dwDomainId, + IN OBJECTREF *orObject); + +}; + +// Cache the bits needed to serialize/deserialize managed objects that will be +// passed across appdomain boundaries during a stackwalk. The serialization is +// performed lazily the first time it's needed and remains valid throughout the +// stackwalk. The last deserialized object is cached and tagged with its +// appdomain context. It's valid as long as we're walking frames within the same +// appdomain. +// +class MarshalCache +{ +public: + MarshalCache() + { + LIMITED_METHOD_CONTRACT; + ZeroMemory(this, sizeof(*this)); + } + + ~MarshalCache() + { + LIMITED_METHOD_CONTRACT; + if (m_pbObj1) + delete [] m_pbObj1; + if (m_pbObj2) + delete [] m_pbObj2; + } + + void EnsureSerializationOK() + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + if ((m_sGC.m_orInput1 != NULL && (m_pbObj1 == NULL || m_cbObj1 == 0)) || + (m_sGC.m_orInput2 != NULL && (m_pbObj2 == NULL || m_cbObj2 == 0))) + { + // Serialization went bad -> Throw exception indicating so. + COMPlusThrow(kSecurityException, IDS_UNMARSHALABLE_DEMAND_OBJECT); + } + } + + void EnsureDeserializationOK() + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + if ((m_pbObj1 != NULL && m_sGC.m_orOutput1 == NULL ) || + (m_pbObj2 != NULL && m_sGC.m_orOutput2 == NULL ) ) + { + // DeSerialization went bad -> Throw exception indicating so. + COMPlusThrow(kSecurityException, IDS_UNMARSHALABLE_DEMAND_OBJECT); + } + } + +#ifndef DACCESS_COMPILE + + // Set the original value of the first cached object. + void SetObject(OBJECTREF orObject) + { + LIMITED_METHOD_CONTRACT; + m_pOriginalDomain = ::GetAppDomain(); + m_sGC.m_orInput1 = orObject; + } + + // Set the original values of both cached objects. + void SetObjects(OBJECTREF orObject1, OBJECTREF orObject2) + { + LIMITED_METHOD_CONTRACT; + m_pOriginalDomain = ::GetAppDomain(); + m_sGC.m_orInput1 = orObject1; + m_sGC.m_orInput2 = orObject2; + } + +#endif //!DACCESS_COMPILE + + // Get a copy of the first object suitable for use in the given appdomain. + OBJECTREF GetObject(AppDomain *pDomain) + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + CheckADValidity(pDomain, ADV_RUNNINGIN); + + // No transition -- just return original object. + if (pDomain == m_pOriginalDomain) { + if (m_fObjectUpdated) + UpdateObjectFinish(); + return m_sGC.m_orInput1; + } + + // We've already deserialized the object into the correct context. + if (pDomain == m_pCachedDomain) + return m_sGC.m_orOutput1; + + // If we've updated the object in a different appdomain from the one we + // originally started in, the cached object will be more up to date than + // the original. Resync the objects. + if (m_fObjectUpdated) + UpdateObjectFinish(); + + // Check whether we've serialized the original input object yet. + if (m_pbObj1 == NULL && m_sGC.m_orInput1 != NULL) + { + AppDomainHelper::MarshalObject(m_pOriginalDomain, + &m_sGC.m_orInput1, + &m_pbObj1, + &m_cbObj1); + EnsureSerializationOK(); + } + + // Deserialize into the correct context. + if (m_pbObj1 != NULL) + { + AppDomainHelper::UnmarshalObject(pDomain, + m_pbObj1, + m_cbObj1, + &m_sGC.m_orOutput1); + EnsureDeserializationOK(); + } + m_pCachedDomain = pDomain; + + return m_sGC.m_orOutput1; + } + + // As above, but retrieve both objects. + OBJECTREF GetObjects(AppDomain *pDomain, OBJECTREF *porObject2) + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + CheckADValidity(pDomain, ADV_RUNNINGIN); + // No transition -- just return original objects. + if (pDomain == m_pOriginalDomain) { + if (m_fObjectUpdated) + UpdateObjectFinish(); + *porObject2 = m_sGC.m_orInput2; + return m_sGC.m_orInput1; + } + + // We've already deserialized the objects into the correct context. + if (pDomain == m_pCachedDomain) { + *porObject2 = m_sGC.m_orOutput2; + return m_sGC.m_orOutput1; + } + + // If we've updated the object in a different appdomain from the one we + // originally started in, the cached object will be more up to date than + // the original. Resync the objects. + if (m_fObjectUpdated) + UpdateObjectFinish(); + + // Check whether we've serialized the original input objects yet. + if ((m_pbObj1 == NULL && m_sGC.m_orInput1 != NULL) || + (m_pbObj2 == NULL && m_sGC.m_orInput2 != NULL)) + { + AppDomainHelper::MarshalObjects(m_pOriginalDomain, + &m_sGC.m_orInput1, + &m_sGC.m_orInput2, + &m_pbObj1, + &m_cbObj1, + &m_pbObj2, + &m_cbObj2); + EnsureSerializationOK(); + + } + if (m_pbObj1 != NULL || m_pbObj2 != NULL) + { + // Deserialize into the correct context. + AppDomainHelper::UnmarshalObjects(pDomain, + m_pbObj1, + m_cbObj1, + m_pbObj2, + m_cbObj2, + &m_sGC.m_orOutput1, + &m_sGC.m_orOutput2); + EnsureDeserializationOK(); + } + m_pCachedDomain = pDomain; + + *porObject2 = m_sGC.m_orOutput2; + return m_sGC.m_orOutput1; + } + + // Change the first object (updating the cacheing information + // appropriately). + void UpdateObject(AppDomain *pDomain, OBJECTREF orObject) + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // The cached serialized blob is now useless. + CheckADValidity(pDomain, ADV_RUNNINGIN); + if (m_pbObj1) + delete [] m_pbObj1; + m_pbObj1 = NULL; + m_cbObj1 = 0; + + // The object we have now is valid in it's own appdomain, so place that + // in the object cache. + m_pCachedDomain = pDomain; + m_sGC.m_orOutput1 = orObject; + + // If the object is updated in the original context, just use the new + // value as is. In this case we have the data to re-marshal the updated + // object as normal, so we can consider the cache fully updated and exit + // now. + if (pDomain == m_pOriginalDomain) { + m_sGC.m_orInput1 = orObject; + m_fObjectUpdated = false; + return; + } + + // We want to avoid re-marshaling the updated value as long as possible + // (it might be updated again before we need its value in a different + // context). So set a flag to indicate that the object must be + // re-marshaled when the value is queried in a new context. + m_fObjectUpdated = true; + } + + // This structure is public only so that it can be GC protected. Do not + // access the fields directly, they change in an unpredictable fashion due + // to the lazy cacheing algorithm. + struct _gc { + OBJECTREF m_orInput1; + OBJECTREF m_orInput2; + OBJECTREF m_orOutput1; + OBJECTREF m_orOutput2; + } m_sGC; + +private: + + // Called after one or more calls to UpdateObject to marshal the updated + // object back into its original context (it's assumed we're called in this + // context). + void UpdateObjectFinish() + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(m_fObjectUpdated && m_pbObj1 == NULL); + } + CONTRACTL_END; + AppDomainHelper::MarshalObject(m_pCachedDomain, + &m_sGC.m_orOutput1, + &m_pbObj1, + &m_cbObj1); + AppDomainHelper::UnmarshalObject(m_pOriginalDomain, + m_pbObj1, + m_cbObj1, + &m_sGC.m_orInput1); + m_fObjectUpdated = false; + } + + BYTE *m_pbObj1; + DWORD m_cbObj1; + BYTE *m_pbObj2; + DWORD m_cbObj2; + AppDomain *m_pCachedDomain; + AppDomain *m_pOriginalDomain; + bool m_fObjectUpdated; +}; + +#endif diff --git a/src/vm/appdomainnative.cpp b/src/vm/appdomainnative.cpp new file mode 100644 index 0000000000..a96c643537 --- /dev/null +++ b/src/vm/appdomainnative.cpp @@ -0,0 +1,1782 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + + + +#include "common.h" +#include "appdomain.hpp" +#include "appdomainnative.hpp" +#ifdef FEATURE_REMOTING +#include "remoting.h" +#include "appdomainhelper.h" +#endif +#include "security.h" +#include "vars.hpp" +#include "eeconfig.h" +#include "appdomain.inl" +#include "eventtrace.h" +#ifndef FEATURE_CORECLR +#include "comutilnative.h" +#endif // !FEATURE_CORECLR +#if defined(FEATURE_APPX) +#include "appxutil.h" +#endif // FEATURE_APPX +#if defined(FEATURE_APPX_BINDER) +#include "clrprivbinderappx.h" +#include "clrprivtypecachewinrt.h" +#endif // FEATURE_APPX_BINDER +#ifdef FEATURE_VERSIONING +#include "../binder/inc/clrprivbindercoreclr.h" +#endif + +#include "clr/fs/path.h" +using namespace clr::fs; + +//************************************************************************ +inline AppDomain *AppDomainNative::ValidateArg(APPDOMAINREF pThis) +{ + CONTRACTL + { + MODE_COOPERATIVE; + DISABLED(GC_TRIGGERS); // can't use this in an FCALL because we're in forbid gc mode until we setup a H_M_F. + THROWS; + } + CONTRACTL_END; + + if (pThis == NULL) + { + COMPlusThrow(kNullReferenceException, W("NullReference_This")); + } + + // Should not get here with a Transparent proxy for the this pointer - + // should have always called through onto the real object +#ifdef FEATURE_REMOTING + _ASSERTE(! CRemotingServices::IsTransparentProxy(OBJECTREFToObject(pThis))); +#endif + + AppDomain* pDomain = (AppDomain*)pThis->GetDomain(); + + if(!pDomain) + { + COMPlusThrow(kNullReferenceException, W("NullReference_This")); + } + + // can only be accessed from within current domain + _ASSERTE(GetAppDomain() == pDomain); + + // should not get here with an invalid appdomain. Once unload it, we won't let anyone else + // in and any threads that are already in will be unwound. + _ASSERTE(SystemDomain::GetAppDomainAtIndex(pDomain->GetIndex()) != NULL); + return pDomain; +} + + +#ifdef FEATURE_REMOTING +//************************************************************************ +FCIMPL5(Object*, AppDomainNative::CreateDomain, StringObject* strFriendlyNameUNSAFE, Object* appdomainSetupUNSAFE, Object* providedEvidenceUNSAFE, Object* creatorsEvidenceUNSAFE, void* parentSecurityDescriptor) +{ + FCALL_CONTRACT; + + struct _gc + { + OBJECTREF retVal; + STRINGREF strFriendlyName; + OBJECTREF appdomainSetup; + OBJECTREF providedEvidence; + OBJECTREF creatorsEvidence; + OBJECTREF entryPointProxy; + } gc; + + ZeroMemory(&gc, sizeof(gc)); + gc.strFriendlyName=(STRINGREF)strFriendlyNameUNSAFE; + gc.appdomainSetup=(OBJECTREF)appdomainSetupUNSAFE; + gc.providedEvidence=(OBJECTREF)providedEvidenceUNSAFE; + gc.creatorsEvidence=(OBJECTREF)creatorsEvidenceUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); + + CreateDomainHelper(&gc.strFriendlyName, &gc.appdomainSetup, &gc.providedEvidence, &gc.creatorsEvidence, parentSecurityDescriptor, &gc.entryPointProxy, &gc.retVal); + + HELPER_METHOD_FRAME_END(); + return OBJECTREFToObject(gc.retVal); +} +FCIMPLEND + +FCIMPL5(Object*, AppDomainNative::CreateInstance, StringObject* strFriendlyNameUNSAFE, Object* appdomainSetupUNSAFE, Object* providedEvidenceUNSAFE, Object* creatorsEvidenceUNSAFE, void* parentSecurityDescriptor) +{ + FCALL_CONTRACT; + + struct _gc + { + OBJECTREF retVal; + STRINGREF strFriendlyName; + OBJECTREF appdomainSetup; + OBJECTREF providedEvidence; + OBJECTREF creatorsEvidence; + OBJECTREF entryPointProxy; + } gc; + + ZeroMemory(&gc, sizeof(gc)); + gc.strFriendlyName=(STRINGREF)strFriendlyNameUNSAFE; + gc.appdomainSetup=(OBJECTREF)appdomainSetupUNSAFE; + gc.providedEvidence=(OBJECTREF)providedEvidenceUNSAFE; + gc.creatorsEvidence=(OBJECTREF)creatorsEvidenceUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); + + CreateDomainHelper(&gc.strFriendlyName, &gc.appdomainSetup, &gc.providedEvidence, &gc.creatorsEvidence, parentSecurityDescriptor, &gc.entryPointProxy, &gc.retVal); + + HELPER_METHOD_FRAME_END(); + return OBJECTREFToObject(gc.entryPointProxy); +} +FCIMPLEND + +void AppDomainNative::CreateDomainHelper (STRINGREF* ppFriendlyName, OBJECTREF* ppAppdomainSetup, OBJECTREF* ppProvidedEvidence, OBJECTREF* ppCreatorsEvidence, void* parentSecurityDescriptor, OBJECTREF* pEntryPointProxy, OBJECTREF* pRetVal) +{ + CONTRACTL + { + MODE_COOPERATIVE; + GC_TRIGGERS; + THROWS; + PRECONDITION(IsProtectedByGCFrame(ppFriendlyName)); + PRECONDITION(IsProtectedByGCFrame(ppAppdomainSetup)); + PRECONDITION(IsProtectedByGCFrame(ppProvidedEvidence)); + PRECONDITION(IsProtectedByGCFrame(ppCreatorsEvidence)); + PRECONDITION(IsProtectedByGCFrame(pEntryPointProxy)); + PRECONDITION(IsProtectedByGCFrame(pRetVal)); + } + CONTRACTL_END; + + + AppDomainCreationHolder pDomain; + + // This helper will send the AppDomain creation notifications for profiler / debugger. + // If it throws, its backout code will also send a notification. + // If it succeeds, then we still need to send a AppDomainCreateFinished notification. + AppDomain::CreateUnmanagedObject(pDomain); + +#ifdef PROFILING_SUPPORTED + EX_TRY +#endif + { + OBJECTREF setupInfo=NULL; + GCPROTECT_BEGIN(setupInfo); + + MethodDescCallSite prepareDataForSetup(METHOD__APP_DOMAIN__PREPARE_DATA_FOR_SETUP); + + ARG_SLOT args[8]; + args[0]=ObjToArgSlot(*ppFriendlyName); + args[1]=ObjToArgSlot(*ppAppdomainSetup); + args[2]=ObjToArgSlot(*ppProvidedEvidence); + args[3]=ObjToArgSlot(*ppCreatorsEvidence); + args[4]=PtrToArgSlot(parentSecurityDescriptor); + args[5]=PtrToArgSlot(NULL); + args[6]=PtrToArgSlot(NULL); + args[7]=PtrToArgSlot(NULL); + + setupInfo = prepareDataForSetup.Call_RetOBJECTREF(args); + +#ifndef FEATURE_CORECLR + // We need to setup domain sorting before any other managed code runs in the domain, since that code + // could end up caching data based on the sorting mode of the domain. + pDomain->InitializeSorting(ppAppdomainSetup); + pDomain->InitializeHashing(ppAppdomainSetup); +#endif + + // We need to ensure that the AppDomainProxy is generated before we call into DoSetup, since + // GetAppDomainProxy will ensure that remoting is correctly configured in the domain. DoSetup can + // end up loading user assemblies into the domain, and those assemblies may require that remoting be + // setup already. For instance, C++/CLI applications may trigger the CRT to try to marshal a + // reference to the default domain into the current domain, which won't work correctly without this + // setup being done. + *pRetVal = pDomain->GetAppDomainProxy(); + + *pEntryPointProxy=pDomain->DoSetup(&setupInfo); + + + GCPROTECT_END(); + + pDomain->CacheStringsForDAC(); + } + +#ifdef PROFILING_SUPPORTED + EX_HOOK + { + // Need the first assembly loaded in to get any data on an app domain. + { + BEGIN_PIN_PROFILER(CORProfilerTrackAppDomainLoads()); + GCX_PREEMP(); + g_profControlBlock.pProfInterface->AppDomainCreationFinished((AppDomainID)(AppDomain *) pDomain, GET_EXCEPTION()->GetHR()); + END_PIN_PROFILER(); + } + } + EX_END_HOOK; + + // Need the first assembly loaded in to get any data on an app domain. + { + BEGIN_PIN_PROFILER(CORProfilerTrackAppDomainLoads()); + GCX_PREEMP(); + g_profControlBlock.pProfInterface->AppDomainCreationFinished((AppDomainID)(AppDomain*) pDomain, S_OK); + END_PIN_PROFILER(); + } +#endif // PROFILING_SUPPORTED + + ETW::LoaderLog::DomainLoad(pDomain, (LPWSTR)(*ppFriendlyName)->GetBuffer()); + + // DoneCreating releases ownership of AppDomain. After this call, there should be no access to pDomain. + pDomain.DoneCreating(); +} +#endif // FEATURE_REMOTING + +void QCALLTYPE AppDomainNative::SetupDomainSecurity(QCall::AppDomainHandle pDomain, + QCall::ObjectHandleOnStack ohEvidence, + IApplicationSecurityDescriptor *pParentSecurityDescriptor, + BOOL fPublishAppDomain) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + struct + { + OBJECTREF orEvidence; + } + gc; + ZeroMemory(&gc, sizeof(gc)); + + GCX_COOP(); + GCPROTECT_BEGIN(gc) + if (ohEvidence.m_ppObject != NULL) + { + gc.orEvidence = ObjectToOBJECTREF(*ohEvidence.m_ppObject); + } + + + // Set up the default AppDomain property. + IApplicationSecurityDescriptor *pSecDesc = pDomain->GetSecurityDescriptor(); + + if (!pSecDesc->IsHomogeneous() && pDomain->IsDefaultDomain()) + { + Security::SetDefaultAppDomainProperty(pSecDesc); + } + // Set up the evidence property in the VM side. + else + { + // If there is no provided evidence then this new appdomain gets the same evidence as the creator. + // + // If there is no provided evidence and this AppDomain is not homogeneous, then it automatically + // is also a default appdomain (for security grant set purposes) + // + // + // If evidence is provided, the new appdomain is not a default appdomain and + // we simply use the provided evidence. + + if (gc.orEvidence == NULL) + { + _ASSERTE(pParentSecurityDescriptor == NULL || pParentSecurityDescriptor->IsDefaultAppDomainEvidence()); + + if (pSecDesc->IsHomogeneous()) + { + // New domain gets default AD evidence + Security::SetDefaultAppDomainEvidenceProperty(pSecDesc); + } + else + { + // New domain gets to be a default AD + Security::SetDefaultAppDomainProperty(pSecDesc); + } + } + } + +#ifdef FEATURE_CAS_POLICY + if (gc.orEvidence != NULL) + { + pSecDesc->SetEvidence(gc.orEvidence); + } +#endif // FEATURE_CAS_POLICY + + // We need to downgrade sharing level if the AppDomain is homogeneous and not fully trusted, or the + // AppDomain is in legacy mode. Effectively, we need to be sure that all assemblies loaded into the + // domain must be fully trusted in order to allow non-GAC sharing. +#ifdef FEATURE_FUSION + if (pDomain->GetSharePolicy() == AppDomain::SHARE_POLICY_ALWAYS) + { + bool fSandboxedHomogenousDomain = false; + if (pSecDesc->IsHomogeneous()) + { + pSecDesc->Resolve(); + fSandboxedHomogenousDomain = !pSecDesc->IsFullyTrusted(); + } + + if (fSandboxedHomogenousDomain || pSecDesc->IsLegacyCasPolicyEnabled()) + { + // We may not be able to reduce sharing policy at this point, if we have already loaded + // some non-GAC assemblies as domain neutral. For this case we must regrettably fail + // the whole operation. + if (!pDomain->ReduceSharePolicyFromAlways()) + { + ThrowHR(COR_E_CANNOT_SET_POLICY); + } + } + } +#endif + + // Now finish the initialization. + pSecDesc->FinishInitialization(); + + // once domain is loaded it is publically available so if you have anything + // that a list interrogator might need access to if it gets a hold of the + // appdomain, then do it above the LoadDomain. + if (fPublishAppDomain) + SystemDomain::LoadDomain(pDomain); + +#ifdef _DEBUG + LOG((LF_APPDOMAIN, LL_INFO100, "AppDomainNative::CreateDomain domain [%d] %p %S\n", pDomain->GetIndex().m_dwIndex, (AppDomain*)pDomain, pDomain->GetFriendlyName())); +#endif + + GCPROTECT_END(); + + END_QCALL; +} + +FCIMPL2(void, AppDomainNative::SetupFriendlyName, AppDomainBaseObject* refThisUNSAFE, StringObject* strFriendlyNameUNSAFE) +{ + FCALL_CONTRACT; + + struct _gc + { + APPDOMAINREF refThis; + STRINGREF strFriendlyName; + } gc; + + gc.refThis = (APPDOMAINREF) refThisUNSAFE; + gc.strFriendlyName = (STRINGREF) strFriendlyNameUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_PROTECT(gc) + + AppDomainRefHolder pDomain(ValidateArg(gc.refThis)); + pDomain->AddRef(); + + // If the user created this domain, need to know this so the debugger doesn't + // go and reset the friendly name that was provided. + pDomain->SetIsUserCreatedDomain(); + + WCHAR* pFriendlyName = NULL; + Thread *pThread = GetThread(); + + CheckPointHolder cph(pThread->m_MarshalAlloc.GetCheckpoint()); //hold checkpoint for autorelease + if (gc.strFriendlyName != NULL) { + WCHAR* pString = NULL; + int iString; + gc.strFriendlyName->RefInterpretGetStringValuesDangerousForGC(&pString, &iString); + if (ClrSafeInt::addition(iString, 1, iString)) + { + pFriendlyName = new (&pThread->m_MarshalAlloc) WCHAR[(iString)]; + + // Check for a valid string allocation + if (pFriendlyName == (WCHAR*)-1) + pFriendlyName = NULL; + else + memcpy(pFriendlyName, pString, iString*sizeof(WCHAR)); + } + } + + pDomain->SetFriendlyName(pFriendlyName); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +#if FEATURE_COMINTEROP + +FCIMPL1(void, AppDomainNative::SetDisableInterfaceCache, AppDomainBaseObject* refThisUNSAFE) +{ + CONTRACTL + { + MODE_COOPERATIVE; + DISABLED(GC_TRIGGERS); // can't use this in an FCALL because we're in forbid gc mode until we setup a H_M_F. + SO_TOLERANT; + THROWS; + } + CONTRACTL_END; + + struct _gc + { + APPDOMAINREF refThis; + } gc; + + gc.refThis = (APPDOMAINREF) refThisUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_PROTECT(gc) + + AppDomainRefHolder pDomain(ValidateArg(gc.refThis)); + pDomain->AddRef(); + + pDomain->SetDisableInterfaceCache(); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +#endif // FEATURE_COMINTEROP + +#ifdef FEATURE_FUSION +FCIMPL1(LPVOID, AppDomainNative::GetFusionContext, AppDomainBaseObject* refThis) +{ + FCALL_CONTRACT; + + LPVOID rv = NULL; + + HELPER_METHOD_FRAME_BEGIN_RET_1(rv); + + AppDomain* pApp = ValidateArg((APPDOMAINREF)refThis); + + rv = pApp->CreateFusionContext(); + + HELPER_METHOD_FRAME_END(); + + return rv; +} +FCIMPLEND +#endif + +FCIMPL1(void*, AppDomainNative::GetSecurityDescriptor, AppDomainBaseObject* refThisUNSAFE) +{ + FCALL_CONTRACT; + + void* pvRetVal = NULL; + APPDOMAINREF refThis = (APPDOMAINREF) refThisUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_1(refThis); + + + pvRetVal = ValidateArg(refThis)->GetSecurityDescriptor(); + + HELPER_METHOD_FRAME_END(); + return pvRetVal; +} +FCIMPLEND + +#ifdef FEATURE_LOADER_OPTIMIZATION +FCIMPL2(void, AppDomainNative::UpdateLoaderOptimization, AppDomainBaseObject* refThisUNSAFE, DWORD optimization) +{ + FCALL_CONTRACT; + + APPDOMAINREF refThis = (APPDOMAINREF) refThisUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_1(refThis); + + ValidateArg(refThis)->SetSharePolicy((AppDomain::SharePolicy) (optimization & AppDomain::SHARE_POLICY_MASK)); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND +#endif // FEATURE_LOADER_OPTIMIZATION + +#ifdef FEATURE_FUSION +FCIMPL3(void, AppDomainNative::UpdateContextProperty, LPVOID fusionContext, StringObject* keyUNSAFE, Object* valueUNSAFE) +{ + FCALL_CONTRACT; + + struct _gc + { + STRINGREF key; + OBJECTREF value; + } gc; + + gc.key = ObjectToSTRINGREF(keyUNSAFE); + gc.value = ObjectToOBJECTREF(valueUNSAFE); + _ASSERTE(gc.key != NULL); + + HELPER_METHOD_FRAME_BEGIN_PROTECT(gc); + + IApplicationContext* pContext = (IApplicationContext*) fusionContext; + + BOOL fFXOnly; + DWORD size = sizeof(fFXOnly); + HRESULT hr = pContext->Get(ACTAG_FX_ONLY, &fFXOnly, &size, 0); + if (hr == HRESULT_FROM_WIN32(ERROR_NOT_FOUND)) + { + fFXOnly = FALSE; + hr = S_FALSE; + } + IfFailThrow(hr); + + if (!fFXOnly) + { + DWORD lgth = gc.key->GetStringLength(); + CQuickBytes qb; + LPWSTR key = (LPWSTR) qb.AllocThrows((lgth+1)*sizeof(WCHAR)); + memcpy(key, gc.key->GetBuffer(), lgth*sizeof(WCHAR)); + key[lgth] = W('\0'); + + AppDomain::SetContextProperty(pContext, key, &gc.value); + } + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND +#endif // FEATURE_FUSION + +/* static */ +INT32 AppDomainNative::ExecuteAssemblyHelper(Assembly* pAssembly, + BOOL bCreatedConsole, + PTRARRAYREF *pStringArgs) +{ + STATIC_CONTRACT_THROWS; + + struct Param + { + Assembly* pAssembly; + PTRARRAYREF *pStringArgs; + INT32 iRetVal; + } param; + param.pAssembly = pAssembly; + param.pStringArgs = pStringArgs; + param.iRetVal = 0; + + EE_TRY_FOR_FINALLY(Param *, pParam, ¶m) + { + pParam->iRetVal = pParam->pAssembly->ExecuteMainMethod(pParam->pStringArgs, FALSE /* waitForOtherThreads */); + } + EE_FINALLY + { +#ifndef FEATURE_PAL + if(bCreatedConsole) + FreeConsole(); +#endif // !FEATURE_PAL + } + EE_END_FINALLY + + return param.iRetVal; +} + +static void UpgradeLinkTimeCheckToLateBoundDemand(MethodDesc* pMeth) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + BOOL isEveryoneFullyTrusted = FALSE; + + struct _gc + { + OBJECTREF refClassNonCasDemands; + OBJECTREF refClassCasDemands; + OBJECTREF refMethodNonCasDemands; + OBJECTREF refMethodCasDemands; + OBJECTREF refThrowable; + } gc; + ZeroMemory(&gc, sizeof(gc)); + + GCPROTECT_BEGIN(gc); + + isEveryoneFullyTrusted = Security::AllDomainsOnStackFullyTrusted(); + + // If all assemblies in the domain are fully trusted then we are not + // going to do any security checks anyway.. + if (isEveryoneFullyTrusted) + { + goto Exit1; + } + + + if (pMeth->RequiresLinktimeCheck()) + { + // Fetch link demand sets from all the places in metadata where we might + // find them (class and method). These might be split into CAS and non-CAS + // sets as well. + Security::RetrieveLinktimeDemands(pMeth, + &gc.refClassCasDemands, + &gc.refClassNonCasDemands, + &gc.refMethodCasDemands, + &gc.refMethodNonCasDemands); + + if (gc.refClassCasDemands == NULL && gc.refClassNonCasDemands == NULL && + gc.refMethodCasDemands == NULL && gc.refMethodNonCasDemands == NULL && + isEveryoneFullyTrusted) + { + // All code access security demands will pass anyway. + goto Exit1; + } + + // The following logic turns link demands on the target method into full + // stack walks in order to close security holes in poorly written + // reflection users. + +#ifdef FEATURE_APTCA + if (Security::IsUntrustedCallerCheckNeeded(pMeth) ) + { + // Check for untrusted caller + // It is possible that wrappers like VBHelper libraries that are + // fully trusted, make calls to public methods that do not have + // safe for Untrusted caller custom attribute set. + // Like all other link demand that gets transformed to a full stack + // walk for reflection, calls to public methods also gets + // converted to full stack walk + + // NOTE: this will always do the APTCA check, regardless of method caller + Security::DoUntrustedCallerChecks(NULL, pMeth, TRUE); + } +#endif + + // CAS Link Demands + if (gc.refClassCasDemands != NULL) + Security::DemandSet(SSWT_LATEBOUND_LINKDEMAND, gc.refClassCasDemands); + + if (gc.refMethodCasDemands != NULL) + Security::DemandSet(SSWT_LATEBOUND_LINKDEMAND, gc.refMethodCasDemands); + + // Non-CAS demands are not applied against a grant + // set, they're standalone. + if (gc.refClassNonCasDemands != NULL) + Security::CheckNonCasDemand(&gc.refClassNonCasDemands); + + if (gc.refMethodNonCasDemands != NULL) + Security::CheckNonCasDemand(&gc.refMethodNonCasDemands); + } + +Exit1:; + GCPROTECT_END(); +} + +FCIMPL3(INT32, AppDomainNative::ExecuteAssembly, AppDomainBaseObject* refThisUNSAFE, + AssemblyBaseObject* assemblyNameUNSAFE, PTRArray* stringArgsUNSAFE) +{ + FCALL_CONTRACT; + + INT32 iRetVal = 0; + + struct _gc + { + APPDOMAINREF refThis; + ASSEMBLYREF assemblyName; + PTRARRAYREF stringArgs; + } gc; + + gc.refThis = (APPDOMAINREF) refThisUNSAFE; + gc.assemblyName = (ASSEMBLYREF) assemblyNameUNSAFE; + gc.stringArgs = (PTRARRAYREF) stringArgsUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); + + AppDomain* pDomain = ValidateArg(gc.refThis); + + if (gc.assemblyName == NULL) + COMPlusThrow(kArgumentNullException, W("ArgumentNull_Generic")); + + if((BaseDomain*) pDomain == SystemDomain::System()) + COMPlusThrow(kUnauthorizedAccessException, W("UnauthorizedAccess_SystemDomain")); + + Assembly* pAssembly = (Assembly*) gc.assemblyName->GetAssembly(); + + if (!pDomain->m_pRootAssembly) + pDomain->m_pRootAssembly = pAssembly; + + MethodDesc *pEntryPointMethod; + { + pEntryPointMethod = pAssembly->GetEntryPoint(); + if (pEntryPointMethod) + { + UpgradeLinkTimeCheckToLateBoundDemand(pEntryPointMethod); + } + } + + BOOL bCreatedConsole = FALSE; + +#ifndef FEATURE_PAL + if (pAssembly->GetManifestFile()->GetSubsystem() == IMAGE_SUBSYSTEM_WINDOWS_CUI) + { + { + GCX_COOP(); + Security::CheckBeforeAllocConsole(pDomain, pAssembly); + } + bCreatedConsole = AllocConsole(); + StackSString codebase; + pAssembly->GetManifestFile()->GetCodeBase(codebase); + SetConsoleTitle(codebase); + } +#endif // !FEATURE_PAL + + // This helper will call FreeConsole() + iRetVal = ExecuteAssemblyHelper(pAssembly, bCreatedConsole, &gc.stringArgs); + + HELPER_METHOD_FRAME_END(); + + return iRetVal; +} +FCIMPLEND + +#ifdef FEATURE_VERSIONING +FCIMPL1(void, + AppDomainNative::CreateContext, + AppDomainBaseObject *refThisUNSAFE) +{ + FCALL_CONTRACT; + + struct _gc + { + APPDOMAINREF refThis; + } gc; + + gc.refThis = (APPDOMAINREF) refThisUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_PROTECT(gc); + + AppDomain* pDomain = ValidateArg(gc.refThis); + + if((BaseDomain*) pDomain == SystemDomain::System()) + { + COMPlusThrow(kUnauthorizedAccessException, W("UnauthorizedAccess_SystemDomain")); + } + + pDomain->CreateFusionContext(); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +void QCALLTYPE AppDomainNative::SetupBindingPaths(__in_z LPCWSTR wszTrustedPlatformAssemblies, __in_z LPCWSTR wszPlatformResourceRoots, __in_z LPCWSTR wszAppPaths, __in_z LPCWSTR wszAppNiPaths, __in_z LPCWSTR appLocalWinMD) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + AppDomain* pDomain = GetAppDomain(); + + SString sTrustedPlatformAssemblies(wszTrustedPlatformAssemblies); + SString sPlatformResourceRoots(wszPlatformResourceRoots); + SString sAppPaths(wszAppPaths); + SString sAppNiPaths(wszAppNiPaths); + SString sappLocalWinMD(appLocalWinMD); + + CLRPrivBinderCoreCLR *pBinder = pDomain->GetTPABinderContext(); + _ASSERTE(pBinder != NULL); + IfFailThrow(pBinder->SetupBindingPaths(sTrustedPlatformAssemblies, + sPlatformResourceRoots, + sAppPaths, + sAppNiPaths)); + +#ifdef FEATURE_COMINTEROP + if (WinRTSupported()) + { + pDomain->SetWinrtApplicationContext(sappLocalWinMD); + } +#endif + + END_QCALL; +} + +#endif // FEATURE_VERSIONING + +FCIMPL12(Object*, AppDomainNative::CreateDynamicAssembly, AppDomainBaseObject* refThisUNSAFE, AssemblyNameBaseObject* assemblyNameUNSAFE, Object* identityUNSAFE, StackCrawlMark* stackMark, Object* requiredPsetUNSAFE, Object* optionalPsetUNSAFE, Object* refusedPsetUNSAFE, U1Array *securityRulesBlobUNSAFE, U1Array *aptcaBlobUNSAFE, INT32 access, INT32 dwFlags, SecurityContextSource securityContextSource) +{ + FCALL_CONTRACT; + + ASSEMBLYREF refRetVal = NULL; + + // + // @TODO: there MUST be a better way to do this... + // + CreateDynamicAssemblyArgs args; + + args.refThis = (APPDOMAINREF) refThisUNSAFE; + args.assemblyName = (ASSEMBLYNAMEREF) assemblyNameUNSAFE; + args.identity = (OBJECTREF) identityUNSAFE; + args.requiredPset = (OBJECTREF) requiredPsetUNSAFE; + args.optionalPset = (OBJECTREF) optionalPsetUNSAFE; + args.refusedPset = (OBJECTREF) refusedPsetUNSAFE; + args.securityRulesBlob = (U1ARRAYREF) securityRulesBlobUNSAFE; + args.aptcaBlob = (U1ARRAYREF) aptcaBlobUNSAFE; + args.loaderAllocator = NULL; + + args.access = access; + args.flags = static_cast(dwFlags); + args.stackMark = stackMark; + args.securityContextSource = securityContextSource; + + HELPER_METHOD_FRAME_BEGIN_RET_PROTECT((CreateDynamicAssemblyArgsGC&)args); + + AppDomain* pAppDomain = ValidateArg(args.refThis); + + Assembly *pAssembly = Assembly::CreateDynamic(pAppDomain, &args); + + refRetVal = (ASSEMBLYREF) pAssembly->GetExposedObject(); + + HELPER_METHOD_FRAME_END(); + return OBJECTREFToObject(refRetVal); +} +FCIMPLEND + +//--------------------------------------------------------------------------------------- +// +// Returns true if the DisableFusionUpdatesFromADManager config switch is turned on. +// +// Arguments: +// adhTarget - AppDomain to get domain manager information about +// + +// static +BOOL QCALLTYPE AppDomainNative::DisableFusionUpdatesFromADManager(QCall::AppDomainHandle adhTarget) +{ + QCALL_CONTRACT; + + BOOL bUpdatesDisabled = FALSE; + + BEGIN_QCALL; + + bUpdatesDisabled = !!(g_pConfig->DisableFusionUpdatesFromADManager()); + + END_QCALL; + + return bUpdatesDisabled; +} + +#ifdef FEATURE_APPX + +// +// Keep in sync with bcl\system\appdomain.cs +// +enum +{ + APPX_FLAGS_INITIALIZED = 0x01, + + APPX_FLAGS_APPX_MODEL = 0x02, + APPX_FLAGS_APPX_DESIGN_MODE = 0x04, + APPX_FLAGS_APPX_NGEN = 0x08, + APPX_FLAGS_APPX_MASK = APPX_FLAGS_APPX_MODEL | + APPX_FLAGS_APPX_DESIGN_MODE | + APPX_FLAGS_APPX_NGEN, + + APPX_FLAGS_API_CHECK = 0x10, +}; + +// static +INT32 QCALLTYPE AppDomainNative::GetAppXFlags() +{ + QCALL_CONTRACT; + + UINT32 flags = APPX_FLAGS_INITIALIZED; + + BEGIN_QCALL; + + if (AppX::IsAppXProcess()) + { + flags |= APPX_FLAGS_APPX_MODEL; + + if (AppX::IsAppXDesignMode()) + flags |= APPX_FLAGS_APPX_DESIGN_MODE; + else + flags |= APPX_FLAGS_API_CHECK; + + if (AppX::IsAppXNGen()) + flags |= APPX_FLAGS_APPX_NGEN; + } + + // + // 0: normal (only check in non-dev-mode APPX) + // 1: always check + // 2: never check + // + switch (g_pConfig->GetWindows8ProfileAPICheckFlag()) + { + case 1: + flags |= APPX_FLAGS_API_CHECK; + break; + case 2: + flags &= ~APPX_FLAGS_API_CHECK; + break; + default: + break; + } + + END_QCALL; + + return flags; +} + +#endif // FEATURE_APPX + +//--------------------------------------------------------------------------------------- +// +// Get the assembly and type containing the AppDomainManager used for the current domain +// +// Arguments: +// adhTarget - AppDomain to get domain manager information about +// retAssembly - [out] assembly which contains the AppDomainManager +// retType - [out] AppDomainManger for the domain +// +// Notes: +// If the AppDomain does not have an AppDomainManager, retAssembly and retType will be null on return. +// + +// static +void QCALLTYPE AppDomainNative::GetAppDomainManagerType(QCall::AppDomainHandle adhTarget, + QCall::StringHandleOnStack shRetAssembly, + QCall::StringHandleOnStack shRetType) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + if (adhTarget->HasAppDomainManagerInfo()) + { + shRetAssembly.Set(adhTarget->GetAppDomainManagerAsm()); + shRetType.Set(adhTarget->GetAppDomainManagerType()); + } + else + { + shRetAssembly.Set(static_cast(NULL)); + shRetType.Set(static_cast(NULL)); + } + + END_QCALL; +} + +//--------------------------------------------------------------------------------------- +// +// Set the assembly and type containing the AppDomainManager to be used for the current domain +// +// Arguments: +// adhTarget - AppDomain to set domain manager information for +// wszAssembly - assembly which contains the AppDomainManager +// wszType - AppDomainManger for the domain +// + +// static +void QCALLTYPE AppDomainNative::SetAppDomainManagerType(QCall::AppDomainHandle adhTarget, + __in_z LPCWSTR wszAssembly, + __in_z LPCWSTR wszType) +{ + CONTRACTL + { + QCALL_CHECK; + PRECONDITION(CheckPointer(wszAssembly)); + PRECONDITION(CheckPointer(wszType)); + PRECONDITION(!GetAppDomain()->HasAppDomainManagerInfo()); + } + CONTRACTL_END; + + BEGIN_QCALL; + + // If the AppDomainManager type is the same as the domain manager setup by the CLR host, then we can + // propagate the host's initialization flags to the new domain as well; + EInitializeNewDomainFlags initializationFlags = eInitializeNewDomainFlags_None; + if (CorHost2::HasAppDomainManagerInfo()) + { + if (wcscmp(CorHost2::GetAppDomainManagerAsm(), wszAssembly) == 0 && + wcscmp(CorHost2::GetAppDomainManagerType(), wszType) == 0) + { + initializationFlags = CorHost2::GetAppDomainManagerInitializeNewDomainFlags(); + } + } + + adhTarget->SetAppDomainManagerInfo(wszAssembly, wszType, initializationFlags); + + // If the initialization flags promise that the domain manager isn't going to modify security, then do a + // pre-resolution of the domain now so that we can do some basic verification of the state later. We + // don't care about the actual result now, just that the resolution took place to compare against later. + if (initializationFlags & eInitializeNewDomainFlags_NoSecurityChanges) + { + BOOL fIsFullyTrusted; + BOOL fIsHomogeneous; + adhTarget->GetSecurityDescriptor()->PreResolve(&fIsFullyTrusted, &fIsHomogeneous); + } + + END_QCALL; +} + +#ifdef FEATURE_APPDOMAINMANAGER_INITOPTIONS + +FCIMPL0(FC_BOOL_RET, AppDomainNative::HasHost) +{ + FCALL_CONTRACT; + FC_RETURN_BOOL(CorHost2::GetHostControl() != NULL); +} +FCIMPLEND + +// +// Callback to the CLR host to register an AppDomainManager->AppDomain ID pair with it. +// +// Arguments: +// punkAppDomainManager - COM reference to the AppDomainManager being registered with the host +// + +// static +void QCALLTYPE AppDomainNative::RegisterWithHost(IUnknown *punkAppDomainManager) +{ + CONTRACTL + { + QCALL_CHECK; + PRECONDITION(CheckPointer(punkAppDomainManager)); + PRECONDITION(CheckPointer(CorHost2::GetHostControl())); + } + CONTRACTL_END; + + BEGIN_QCALL; + + EnsureComStarted(); + + IHostControl *pHostControl = CorHost2::GetHostControl(); + ADID dwDomainId = SystemDomain::GetCurrentDomain()->GetId(); + HRESULT hr = S_OK; + + BEGIN_SO_TOLERANT_CODE_CALLING_HOST(GetThread()); + hr = pHostControl->SetAppDomainManager(dwDomainId.m_dwId, punkAppDomainManager); + END_SO_TOLERANT_CODE_CALLING_HOST; + + if (FAILED(hr)) + { + ThrowHR(hr); + } + + END_QCALL; +} +#endif // FEATURE_APPDOMAINMANAGER_INITOPTIONS + +FCIMPL1(void, AppDomainNative::SetHostSecurityManagerFlags, DWORD dwFlags); +{ + FCALL_CONTRACT; + + HELPER_METHOD_FRAME_BEGIN_0(); + + GetThread()->GetDomain()->GetSecurityDescriptor()->SetHostSecurityManagerFlags(dwFlags); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +// static +void QCALLTYPE AppDomainNative::SetSecurityHomogeneousFlag(QCall::AppDomainHandle adhTarget, + BOOL fRuntimeSuppliedHomogenousGrantSet) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + IApplicationSecurityDescriptor *pAppSecDesc = adhTarget->GetSecurityDescriptor(); + pAppSecDesc->SetHomogeneousFlag(fRuntimeSuppliedHomogenousGrantSet); + + END_QCALL; +} + +#ifdef FEATURE_CAS_POLICY + +// static +void QCALLTYPE AppDomainNative::SetLegacyCasPolicyEnabled(QCall::AppDomainHandle adhTarget) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + IApplicationSecurityDescriptor *pAppSecDesc = adhTarget->GetSecurityDescriptor(); + pAppSecDesc->SetLegacyCasPolicyEnabled(); + + END_QCALL; +} + +// static +BOOL QCALLTYPE AppDomainNative::IsLegacyCasPolicyEnabled(QCall::AppDomainHandle adhTarget) +{ + QCALL_CONTRACT; + + BOOL fLegacyCasPolicy = FALSE; + + BEGIN_QCALL; + + IApplicationSecurityDescriptor *pAppSecDesc = adhTarget->GetSecurityDescriptor(); + fLegacyCasPolicy = !!pAppSecDesc->IsLegacyCasPolicyEnabled(); + + END_QCALL; + + return fLegacyCasPolicy; +} + +#endif // FEATURE_CAS_POLICY + +#ifdef FEATURE_APTCA + +// static +void QCALLTYPE AppDomainNative::SetCanonicalConditionalAptcaList(QCall::AppDomainHandle adhTarget, + LPCWSTR wszCanonicalConditionalAptcaList) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + IApplicationSecurityDescriptor *pAppSecDesc = adhTarget->GetSecurityDescriptor(); + + GCX_COOP(); + pAppSecDesc->SetCanonicalConditionalAptcaList(wszCanonicalConditionalAptcaList); + + END_QCALL; +} + +#endif // FEATURE_APTCA + +FCIMPL1(Object*, AppDomainNative::GetFriendlyName, AppDomainBaseObject* refThisUNSAFE) +{ + FCALL_CONTRACT; + + STRINGREF str = NULL; + APPDOMAINREF refThis = (APPDOMAINREF) refThisUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_1(refThis); + + AppDomain* pApp = ValidateArg(refThis); + + LPCWSTR wstr = pApp->GetFriendlyName(); + if (wstr) + str = StringObject::NewString(wstr); + + HELPER_METHOD_FRAME_END(); + return OBJECTREFToObject(str); +} +FCIMPLEND + +FCIMPL1(FC_BOOL_RET, AppDomainNative::IsDefaultAppDomainForEvidence, AppDomainBaseObject* refThisUNSAFE) +{ + FCALL_CONTRACT; + + BOOL retVal = FALSE; + APPDOMAINREF refThis = (APPDOMAINREF) refThisUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_1(refThis); + + AppDomain* pApp = ValidateArg((APPDOMAINREF) refThisUNSAFE); + retVal = pApp->GetSecurityDescriptor()->IsDefaultAppDomainEvidence(); + + HELPER_METHOD_FRAME_END(); + FC_RETURN_BOOL(retVal); +} +FCIMPLEND + +FCIMPL2(Object*, AppDomainNative::GetAssemblies, AppDomainBaseObject* refThisUNSAFE, CLR_BOOL forIntrospection); +{ + FCALL_CONTRACT; + + struct _gc + { + PTRARRAYREF AsmArray; + APPDOMAINREF refThis; + } gc; + + gc.AsmArray = NULL; + gc.refThis = (APPDOMAINREF) refThisUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); + + MethodTable * pAssemblyClass = MscorlibBinder::GetClass(CLASS__ASSEMBLY); + + AppDomain * pApp = ValidateArg(gc.refThis); + + // Allocate an array with as many elements as there are assemblies in this + // appdomain. This will usually be correct, but there may be assemblies + // that are still loading, and those won't be included in the array of + // loaded assemblies. When that happens, the array will have some trailing + // NULL entries; those entries will need to be trimmed. + size_t nArrayElems = pApp->m_Assemblies.GetCount(pApp); + gc.AsmArray = (PTRARRAYREF) AllocateObjectArray( + (DWORD)nArrayElems, + pAssemblyClass); + + size_t numAssemblies = 0; + { + // Iterate over the loaded assemblies in the appdomain, and add each one to + // to the array. Quit when the array is full, in case assemblies have been + // loaded into this appdomain, on another thread. + AppDomain::AssemblyIterator i = pApp->IterateAssembliesEx((AssemblyIterationFlags)( + kIncludeLoaded | + (forIntrospection ? kIncludeIntrospection : kIncludeExecution))); + CollectibleAssemblyHolder pDomainAssembly; + + while (i.Next(pDomainAssembly.This()) && (numAssemblies < nArrayElems)) + { + // Do not change this code. This is done this way to + // prevent a GC hole in the SetObjectReference() call. The compiler + // is free to pick the order of evaluation. + OBJECTREF o = (OBJECTREF)pDomainAssembly->GetExposedAssemblyObject(); + if (o == NULL) + { // The assembly was collected and is not reachable from managed code anymore + continue; + } + gc.AsmArray->SetAt(numAssemblies++, o); + // If it is a collectible assembly, it is now referenced from the managed world, so we can + // release the native reference in the holder + } + } + + // If we didn't fill the array, allocate a new array that is exactly the + // right size, and copy the data to it. + if (numAssemblies < nArrayElems) + { + PTRARRAYREF AsmArray2; + AsmArray2 = (PTRARRAYREF) AllocateObjectArray( + (DWORD)numAssemblies, + pAssemblyClass); + + for (size_t ix = 0; ix < numAssemblies; ++ix) + { + AsmArray2->SetAt(ix, gc.AsmArray->GetAt(ix)); + } + + gc.AsmArray = AsmArray2; + } + + HELPER_METHOD_FRAME_END(); + return OBJECTREFToObject(gc.AsmArray); +} // AppDomainNative::GetAssemblies +FCIMPLEND + + +FCIMPL1(void, AppDomainNative::Unload, INT32 dwId) +{ + FCALL_CONTRACT; + + HELPER_METHOD_FRAME_BEGIN_0(); + + IfFailThrow(AppDomain::UnloadById(ADID(dwId),TRUE)); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +FCIMPL1(FC_BOOL_RET, AppDomainNative::IsDomainIdValid, INT32 dwId) +{ + FCALL_CONTRACT; + + BOOL retVal = FALSE; + HELPER_METHOD_FRAME_BEGIN_RET_0() + + AppDomainFromIDHolder ad((ADID)dwId, TRUE); + retVal=!ad.IsUnloaded(); + HELPER_METHOD_FRAME_END(); + FC_RETURN_BOOL(retVal); +} +FCIMPLEND + +#ifdef FEATURE_REMOTING +FCIMPL0(Object*, AppDomainNative::GetDefaultDomain) +{ + FCALL_CONTRACT; + + APPDOMAINREF rv = NULL; + + HELPER_METHOD_FRAME_BEGIN_RET_1(rv); + + if (GetThread()->GetDomain()->IsDefaultDomain()) + rv = (APPDOMAINREF) SystemDomain::System()->DefaultDomain()->GetExposedObject(); + else + rv = (APPDOMAINREF) SystemDomain::System()->DefaultDomain()->GetAppDomainProxy(); + + HELPER_METHOD_FRAME_END(); + return OBJECTREFToObject(rv); +} +FCIMPLEND +#endif + +FCIMPL1(INT32, AppDomainNative::GetId, AppDomainBaseObject* refThisUNSAFE) +{ + FCALL_CONTRACT; + + INT32 iRetVal = 0; + APPDOMAINREF refThis = (APPDOMAINREF) refThisUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_1(refThis); + + AppDomain* pApp = ValidateArg(refThis); + // can only be accessed from within current domain + _ASSERTE(GetThread()->GetDomain() == pApp); + + iRetVal = pApp->GetId().m_dwId; + + HELPER_METHOD_FRAME_END(); + return iRetVal; +} +FCIMPLEND + +FCIMPL1(void, AppDomainNative::ChangeSecurityPolicy, AppDomainBaseObject* refThisUNSAFE) +{ + FCALL_CONTRACT; + + APPDOMAINREF refThis = (APPDOMAINREF) refThisUNSAFE; + HELPER_METHOD_FRAME_BEGIN_1(refThis); + AppDomain* pApp = ValidateArg(refThis); + +#ifdef FEATURE_FUSION + + // We do not support sharing behavior of ALWAYS when using app-domain local security config + if (pApp->GetSharePolicy() == AppDomain::SHARE_POLICY_ALWAYS) + { + // We may not be able to reduce sharing policy at this point, if we have already loaded + // some non-GAC assemblies as domain neutral. For this case we must regrettably fail + // the whole operation. + if (!pApp->ReduceSharePolicyFromAlways()) + ThrowHR(COR_E_CANNOT_SET_POLICY); + } +#endif + pApp->GetSecurityDescriptor()->SetPolicyLevelFlag(); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + + +FCIMPL2(Object*, AppDomainNative::IsStringInterned, AppDomainBaseObject* refThisUNSAFE, StringObject* pStringUNSAFE) +{ + FCALL_CONTRACT; + + APPDOMAINREF refThis = (APPDOMAINREF)ObjectToOBJECTREF(refThisUNSAFE); + STRINGREF refString = ObjectToSTRINGREF(pStringUNSAFE); + STRINGREF* prefRetVal = NULL; + + HELPER_METHOD_FRAME_BEGIN_RET_2(refThis, refString); + + ValidateArg(refThis); + + if (refString == NULL) + COMPlusThrow(kArgumentNullException, W("ArgumentNull_String")); + + prefRetVal = refThis->GetDomain()->IsStringInterned(&refString); + + HELPER_METHOD_FRAME_END(); + + if (prefRetVal == NULL) + return NULL; + + return OBJECTREFToObject(*prefRetVal); +} +FCIMPLEND + +FCIMPL2(Object*, AppDomainNative::GetOrInternString, AppDomainBaseObject* refThisUNSAFE, StringObject* pStringUNSAFE) +{ + FCALL_CONTRACT; + + STRINGREF refRetVal = NULL; + APPDOMAINREF refThis = (APPDOMAINREF) refThisUNSAFE; + STRINGREF pString = (STRINGREF) pStringUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_2(refThis, pString); + + ValidateArg(refThis); + + if (pString == NULL) + COMPlusThrow(kArgumentNullException, W("ArgumentNull_String")); + + STRINGREF* stringVal = refThis->GetDomain()->GetOrInternString(&pString); + if (stringVal != NULL) + { + refRetVal = *stringVal; + } + + HELPER_METHOD_FRAME_END(); + return OBJECTREFToObject(refRetVal); +} +FCIMPLEND + + +FCIMPL1(Object*, AppDomainNative::GetDynamicDir, AppDomainBaseObject* refThisUNSAFE) +{ + FCALL_CONTRACT; + + STRINGREF str = NULL; +#ifdef FEATURE_FUSION + APPDOMAINREF refThis = (APPDOMAINREF) refThisUNSAFE; + HELPER_METHOD_FRAME_BEGIN_RET_1(refThis); + + AppDomain *pDomain = ValidateArg(refThis); + str = StringObject::NewString(pDomain->GetDynamicDir()); + HELPER_METHOD_FRAME_END(); +#endif + return OBJECTREFToObject(str); +} +FCIMPLEND + +// static +void QCALLTYPE AppDomainNative::GetGrantSet(QCall::AppDomainHandle adhTarget, + QCall::ObjectHandleOnStack retGrantSet) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + IApplicationSecurityDescriptor *pSecDesc = adhTarget->GetSecurityDescriptor(); + + GCX_COOP(); + pSecDesc->Resolve(); + retGrantSet.Set(pSecDesc->GetGrantedPermissionSet()); + + END_QCALL; +} + + +FCIMPL1(FC_BOOL_RET, AppDomainNative::IsUnloadingForcedFinalize, AppDomainBaseObject* refThisUNSAFE) +{ + FCALL_CONTRACT; + + BOOL retVal = FALSE; + APPDOMAINREF refThis = (APPDOMAINREF) refThisUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_1(refThis); + + AppDomain* pApp = ValidateArg((APPDOMAINREF)refThis); + retVal = pApp->IsFinalized(); + + HELPER_METHOD_FRAME_END(); + FC_RETURN_BOOL(retVal); +} +FCIMPLEND + +FCIMPL1(FC_BOOL_RET, AppDomainNative::IsFinalizingForUnload, AppDomainBaseObject* refThisUNSAFE) +{ + FCALL_CONTRACT; + + BOOL retVal = FALSE; + APPDOMAINREF refThis = (APPDOMAINREF) refThisUNSAFE; + HELPER_METHOD_FRAME_BEGIN_RET_1(refThis); + + AppDomain* pApp = ValidateArg(refThis); + retVal = pApp->IsFinalizing(); + + HELPER_METHOD_FRAME_END(); + FC_RETURN_BOOL(retVal); +} +FCIMPLEND + +FCIMPL2(StringObject*, AppDomainNative::nApplyPolicy, AppDomainBaseObject* refThisUNSAFE, AssemblyNameBaseObject* refAssemblyNameUNSAFE) +{ + FCALL_CONTRACT; + + struct _gc + { + APPDOMAINREF refThis; + ASSEMBLYNAMEREF assemblyName; + STRINGREF rv; + } gc; + + gc.refThis = (APPDOMAINREF)refThisUNSAFE; + gc.assemblyName = (ASSEMBLYNAMEREF) refAssemblyNameUNSAFE; + gc.rv = NULL; + + HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); + + AppDomain* pDomain; + pDomain = ValidateArg(gc.refThis); + + if (gc.assemblyName == NULL) + { + COMPlusThrow(kArgumentNullException, W("ArgumentNull_AssemblyName")); + } + if( (gc.assemblyName->GetSimpleName() == NULL) ) + { + COMPlusThrow(kArgumentException, W("Format_StringZeroLength")); + } + Thread *pThread = GetThread(); + CheckPointHolder cph(pThread->m_MarshalAlloc.GetCheckpoint()); //hold checkpoint for autorelease + + // Initialize spec + AssemblySpec spec; + spec.InitializeSpec(&(pThread->m_MarshalAlloc), + &gc.assemblyName, + FALSE, /*fIsStringized*/ + FALSE /*fForIntrospection*/ + ); + + StackSString sDisplayName; + +#ifdef FEATURE_FUSION + { + GCX_PREEMP(); + + SafeComHolderPreemp pAssemblyName(NULL); + SafeComHolderPreemp pBoundName(NULL); + IfFailThrow(spec.CreateFusionName(&pAssemblyName)); + HRESULT hr = PreBindAssembly(pDomain->GetFusionContext(), + pAssemblyName, + NULL, // pAsmParent (only needed to see if parent is loadfrom - in this case, we always want it to load in the normal ctx) + &pBoundName, + NULL // pvReserved + ); + if (FAILED(hr) && hr != FUSION_E_REF_DEF_MISMATCH) + { + ThrowHR(hr); + } + + FusionBind::GetAssemblyNameDisplayName(pBoundName, /*modifies*/sDisplayName, 0 /*flags*/); + } +#else + spec.GetFileOrDisplayName(0,sDisplayName); +#endif + + gc.rv = StringObject::NewString(sDisplayName); + + HELPER_METHOD_FRAME_END(); + return (StringObject*)OBJECTREFToObject(gc.rv); +} +FCIMPLEND + +FCIMPL1(UINT32, AppDomainNative::GetAppDomainId, AppDomainBaseObject* refThisUNSAFE) +{ + FCALL_CONTRACT; + + FCUnique(0x91); + + UINT32 retVal = 0; + APPDOMAINREF domainRef = (APPDOMAINREF) refThisUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_1(domainRef); + + AppDomain* pDomain = ValidateArg(domainRef); + retVal = pDomain->GetId().m_dwId; + + HELPER_METHOD_FRAME_END(); + return retVal; +} +FCIMPLEND + +FCIMPL1(void , AppDomainNative::PublishAnonymouslyHostedDynamicMethodsAssembly, AssemblyBaseObject * pAssemblyUNSAFE); +{ + CONTRACTL + { + FCALL_CHECK; + } + CONTRACTL_END; + + ASSEMBLYREF refAssembly = (ASSEMBLYREF)ObjectToOBJECTREF(pAssemblyUNSAFE); + if (refAssembly == NULL) + FCThrowResVoid(kArgumentNullException, W("Arg_InvalidHandle")); + + DomainAssembly* pDomainAssembly = refAssembly->GetDomainAssembly(); + + pDomainAssembly->GetAppDomain()->SetAnonymouslyHostedDynamicMethodsAssembly(pDomainAssembly); +} +FCIMPLEND + +#ifdef FEATURE_CORECLR + +void QCALLTYPE AppDomainNative::SetNativeDllSearchDirectories(__in_z LPCWSTR wszNativeDllSearchDirectories) +{ + CONTRACTL + { + QCALL_CHECK; + PRECONDITION(CheckPointer(wszNativeDllSearchDirectories)); + } + CONTRACTL_END; + + BEGIN_QCALL; + AppDomain *pDomain = GetAppDomain(); + + SString sDirectories(wszNativeDllSearchDirectories); + + if(sDirectories.GetCount() > 0) + { + SString::CIterator start = sDirectories.Begin(); + SString::CIterator itr = sDirectories.Begin(); + SString::CIterator end = sDirectories.End(); + SString qualifiedPath; + + while (itr != end) + { + start = itr; + BOOL found = sDirectories.Find(itr, PATH_SEPARATOR_CHAR_W); + if (!found) + { + itr = end; + } + + SString qualifiedPath(sDirectories,start,itr); + + if (found) + { + itr++; + } + + unsigned len = qualifiedPath.GetCount(); + + if (len > 0) + { + if (qualifiedPath[len - 1] != DIRECTORY_SEPARATOR_CHAR_W) + { + qualifiedPath.Append(DIRECTORY_SEPARATOR_CHAR_W); + } + + NewHolder stringHolder (new SString(qualifiedPath)); + IfFailThrow(pDomain->m_NativeDllSearchDirectories.Append(stringHolder.GetValue())); + stringHolder.SuppressRelease(); + } + } + } + END_QCALL; +} + +#endif // FEATURE_CORECLR + +#ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING +FCIMPL0(void, AppDomainNative::EnableMonitoring) +{ + FCALL_CONTRACT; + + HELPER_METHOD_FRAME_BEGIN_0(); + + EnableARM(); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +FCIMPL0(FC_BOOL_RET, AppDomainNative::MonitoringIsEnabled) +{ + FCALL_CONTRACT; + + FC_GC_POLL_NOT_NEEDED(); + + FC_RETURN_BOOL(g_fEnableARM); +} +FCIMPLEND + +FCIMPL1(INT64, AppDomainNative::GetTotalProcessorTime, AppDomainBaseObject* refThisUNSAFE) +{ + FCALL_CONTRACT; + + INT64 i64RetVal = -1; + + if (g_fEnableARM) + { + APPDOMAINREF refThis = (APPDOMAINREF) refThisUNSAFE; + HELPER_METHOD_FRAME_BEGIN_RET_1(refThis); + + AppDomain* pDomain = ValidateArg(refThis); + // can only be accessed from within current domain + _ASSERTE(GetThread()->GetDomain() == pDomain); + + i64RetVal = (INT64)pDomain->QueryProcessorUsage(); + + HELPER_METHOD_FRAME_END(); + } + + return i64RetVal; +} +FCIMPLEND + +FCIMPL1(INT64, AppDomainNative::GetTotalAllocatedMemorySize, AppDomainBaseObject* refThisUNSAFE) +{ + FCALL_CONTRACT; + + INT64 i64RetVal = -1; + + if (g_fEnableARM) + { + APPDOMAINREF refThis = (APPDOMAINREF) refThisUNSAFE; + HELPER_METHOD_FRAME_BEGIN_RET_1(refThis); + + AppDomain* pDomain = ValidateArg(refThis); + // can only be accessed from within current domain + _ASSERTE(GetThread()->GetDomain() == pDomain); + + i64RetVal = (INT64)pDomain->GetAllocBytes(); + + HELPER_METHOD_FRAME_END(); + } + + return i64RetVal; +} +FCIMPLEND + +FCIMPL1(INT64, AppDomainNative::GetLastSurvivedMemorySize, AppDomainBaseObject* refThisUNSAFE) +{ + FCALL_CONTRACT; + + INT64 i64RetVal = -1; + + if (g_fEnableARM) + { + APPDOMAINREF refThis = (APPDOMAINREF) refThisUNSAFE; + HELPER_METHOD_FRAME_BEGIN_RET_1(refThis); + + AppDomain* pDomain = ValidateArg(refThis); + // can only be accessed from within current domain + _ASSERTE(GetThread()->GetDomain() == pDomain); + + i64RetVal = (INT64)pDomain->GetSurvivedBytes(); + + HELPER_METHOD_FRAME_END(); + } + + return i64RetVal; +} +FCIMPLEND + +FCIMPL0(INT64, AppDomainNative::GetLastSurvivedProcessMemorySize) +{ + FCALL_CONTRACT; + + INT64 i64RetVal = -1; + + if (g_fEnableARM) + { + i64RetVal = SystemDomain::GetTotalSurvivedBytes(); + } + + return i64RetVal; + + +} +FCIMPLEND +#endif // FEATURE_APPDOMAIN_RESOURCE_MONITORING + +#if defined(FEATURE_APPX_BINDER) +ICLRPrivBinder * QCALLTYPE AppDomainNative::CreateDesignerContext(LPCWSTR *rgPaths, + UINT cPaths, + BOOL fShared) +{ + QCALL_CONTRACT; + + ICLRPrivBinder *pRetVal = nullptr; + + BEGIN_QCALL; + ReleaseHolder pBinder; + + // The runtime check is done on the managed side to enable the debugger to use + // FuncEval to create designer contexts outside of DesignMode. + _ASSERTE(AppX::IsAppXDesignMode() || (AppX::IsAppXProcess() && CORDebuggerAttached())); + + AppDomain *pAppDomain = GetAppDomain(); + + pBinder = CLRPrivBinderAppX::CreateParentedBinder(fShared ? pAppDomain->GetLoadContextHostBinder() : pAppDomain->GetSharedContextHostBinder(), CLRPrivTypeCacheWinRT::GetOrCreateTypeCache(), rgPaths, cPaths, fShared /* fCanUseNativeImages */); + + { + BaseDomain::LockHolder lh(pAppDomain); + pAppDomain->AppDomainInterfaceReleaseList.Append(pRetVal); + } + pBinder.SuppressRelease(); + pRetVal = pBinder; + + END_QCALL; + + return pRetVal; +} + +void QCALLTYPE AppDomainNative::SetCurrentDesignerContext(BOOL fDesignerContext, ICLRPrivBinder *newContext) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + if (fDesignerContext) + { + GetAppDomain()->SetCurrentContextHostBinder(newContext); + } + else + { + // Managed code is responsible for ensuring this isn't called more than once per AppDomain. + GetAppDomain()->SetSharedContextHostBinder(newContext); + } + + END_QCALL; +} +#endif // defined(FEATURE_APPX_BINDER) + diff --git a/src/vm/appdomainnative.hpp b/src/vm/appdomainnative.hpp new file mode 100644 index 0000000000..ee3af20aaa --- /dev/null +++ b/src/vm/appdomainnative.hpp @@ -0,0 +1,153 @@ +// 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. + + + +/*============================================================ +** +** Header: AppDomainNative.hpp +** +** Purpose: Implements native methods for AppDomains +** +** +===========================================================*/ +#ifndef _APPDOMAINNATIVE_H +#define _APPDOMAINNATIVE_H + +#include "qcall.h" + +class AppDomainNative +{ +public: + static AppDomain *ValidateArg(APPDOMAINREF pThis); +#ifdef FEATURE_REMOTING + static FCDECL5(Object*, CreateDomain, StringObject* strFriendlyNameUNSAFE, Object* appdomainSetup, Object* providedEvidenceUNSAFE, Object* creatorsEvidenceUNSAFE, void* parentSecurityDescriptor); + static FCDECL5(Object*, CreateInstance, StringObject* strFriendlyNameUNSAFE, Object* appdomainSetup, Object* providedEvidenceUNSAFE, Object* creatorsEvidenceUNSAFE, void* parentSecurityDescriptor); +#endif + static FCDECL2(void, SetupFriendlyName, AppDomainBaseObject* refThisUNSAFE, StringObject* strFriendlyNameUNSAFE); +#if FEATURE_COMINTEROP + static FCDECL1(void, SetDisableInterfaceCache, AppDomainBaseObject* refThisUNSAFE); +#endif // FEATURE_COMINTEROP + static FCDECL1(void*, GetSecurityDescriptor, AppDomainBaseObject* refThisUNSAFE); +#ifdef FEATURE_LOADER_OPTIMIZATION + static FCDECL2(void, UpdateLoaderOptimization, AppDomainBaseObject* refThisUNSAFE, DWORD optimization); +#endif // FEATURE_LOADER_OPTIMIZATION + + static FCDECL12(Object*, CreateDynamicAssembly, AppDomainBaseObject* refThisUNSAFE, AssemblyNameBaseObject* assemblyNameUNSAFE, Object* identityUNSAFE, StackCrawlMark* stackMark, Object* requiredPsetUNSAFE, Object* optionalPsetUNSAFE, Object* refusedPsetUNSAFE, U1Array* securityRulesBlobUNSAFE, U1Array* aptcaBlobUNSAFE, INT32 access, INT32 flags, SecurityContextSource securityContextSource); +#ifdef FEATURE_APPDOMAINMANAGER_INITOPTIONS + static FCDECL0(FC_BOOL_RET, HasHost); +#endif // FEATURE_APPDOMAINMANAGER_INITOPTIONS + static FCDECL1(void, SetHostSecurityManagerFlags, DWORD dwFlags); + static FCDECL1(Object*, GetFriendlyName, AppDomainBaseObject* refThisUNSAFE); + static FCDECL1(FC_BOOL_RET, IsDefaultAppDomainForEvidence, AppDomainBaseObject* refThisUNSAFE); + static FCDECL2(Object*, GetAssemblies, AppDomainBaseObject* refThisUNSAFE, CLR_BOOL fForIntrospection); + static FCDECL2(Object*, GetOrInternString, AppDomainBaseObject* refThisUNSAFE, StringObject* pStringUNSAFE); + static FCDECL3(INT32, ExecuteAssembly, AppDomainBaseObject* refThisUNSAFE, AssemblyBaseObject* assemblyNameUNSAFE, PTRArray* stringArgsUNSAFE); +#ifdef FEATURE_VERSIONING + static FCDECL1(void, CreateContext, AppDomainBaseObject *refThisUNSAFE); + static void QCALLTYPE SetupBindingPaths(__in_z LPCWSTR wszTrustedPlatformAssemblies, __in_z LPCWSTR wszPlatformResourceRoots, __in_z LPCWSTR wszAppPaths, __in_z LPCWSTR wszAppNiPaths, __in_z LPCWSTR appLocalWinMD); +#endif // FEATURE_VERSIONING + static FCDECL1(void, Unload, INT32 dwId); + static FCDECL1(Object*, GetDynamicDir, AppDomainBaseObject* refThisUNSAFE); + static FCDECL1(INT32, GetId, AppDomainBaseObject* refThisUNSAFE); + static FCDECL1(INT32, GetIdForUnload, AppDomainBaseObject* refDomainUNSAFE); + static FCDECL1(FC_BOOL_RET, IsDomainIdValid, INT32 dwId); + static FCDECL1(FC_BOOL_RET, IsFinalizingForUnload, AppDomainBaseObject* refThisUNSAFE); + static FCDECL1(void, ForceToSharedDomain, Object* pObjectUNSAFE); + static FCDECL1(void, ChangeSecurityPolicy, AppDomainBaseObject* refThisUNSAFE); +#ifdef FEATURE_REMOTING + static FCDECL0(Object*, GetDefaultDomain); +#endif + static FCDECL1(LPVOID, GetFusionContext, AppDomainBaseObject* refThis); + static FCDECL2(Object*, IsStringInterned, AppDomainBaseObject* refThis, StringObject* pString); + static FCDECL1(FC_BOOL_RET, IsUnloadingForcedFinalize, AppDomainBaseObject* refThis); + static FCDECL3(void, UpdateContextProperty, LPVOID fusionContext, StringObject* key, Object* value); + static FCDECL2(StringObject*, nApplyPolicy, AppDomainBaseObject* refThisUNSAFE, AssemblyNameBaseObject* assemblyNameUNSAFE); + static FCDECL2(FC_BOOL_RET, IsFrameworkAssembly, AppDomainBaseObject* refThisUNSAFE, AssemblyNameBaseObject* refAssemblyNameUNSAFE); + static FCDECL1(UINT32, GetAppDomainId, AppDomainBaseObject* refThisUNSAFE); + static FCDECL1(void , PublishAnonymouslyHostedDynamicMethodsAssembly, AssemblyBaseObject * pAssemblyUNSAFE); +#ifdef FEATURE_CORECLR + static void QCALLTYPE SetNativeDllSearchDirectories(__in_z LPCWSTR wszAssembly); +#endif // FEATURE_CORECLR + +#ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING + static FCDECL0(void, EnableMonitoring); + static FCDECL0(FC_BOOL_RET, MonitoringIsEnabled); + static FCDECL1(INT64, GetTotalProcessorTime, AppDomainBaseObject* refThisUNSAFE); + static FCDECL1(INT64, GetTotalAllocatedMemorySize, AppDomainBaseObject* refThisUNSAFE); + static FCDECL1(INT64, GetLastSurvivedMemorySize, AppDomainBaseObject* refThisUNSAFE); + static FCDECL0(INT64, GetLastSurvivedProcessMemorySize); +#endif // FEATURE_APPDOMAIN_RESOURCE_MONITORING + +private: + static INT32 ExecuteAssemblyHelper(Assembly* pAssembly, + BOOL bCreatedConsole, + PTRARRAYREF *pStringArgs); +#ifdef FEATURE_REMOTING + static void CreateDomainHelper (STRINGREF* ppFriendlyName, OBJECTREF* ppAppdomainSetup, OBJECTREF* ppProvidedEvidence, OBJECTREF* ppCreatorsEvidence, void* parentSecurityDescriptor, OBJECTREF* pEntryPointProxy, OBJECTREF* pRetVal); +#endif + +public: + static + void QCALLTYPE SetupDomainSecurity(QCall::AppDomainHandle pDomain, + QCall::ObjectHandleOnStack ohEvidence, + IApplicationSecurityDescriptor *pParentSecurityDescriptor, + BOOL fPublishAppDomain); + + static + void QCALLTYPE GetGrantSet(QCall::AppDomainHandle adhTarget, + QCall::ObjectHandleOnStack retGrantSet); + + + static + BOOL QCALLTYPE DisableFusionUpdatesFromADManager(QCall::AppDomainHandle adhTarget); + +#ifdef FEATURE_APPX + static + INT32 QCALLTYPE GetAppXFlags(); +#endif + + static + void QCALLTYPE GetAppDomainManagerType(QCall::AppDomainHandle adhTarget, + QCall::StringHandleOnStack shRetAssembly, + QCall::StringHandleOnStack shRetType); + + static + void QCALLTYPE SetAppDomainManagerType(QCall::AppDomainHandle adhTarget, + __in_z LPCWSTR wszAssembly, + __in_z LPCWSTR wszType); + + static + void QCALLTYPE SetSecurityHomogeneousFlag(QCall::AppDomainHandle adhTarget, + BOOL fRuntimeSuppliedHomgenousGrantSet); + +#ifdef FEATURE_CAS_POLICY + static + void QCALLTYPE SetLegacyCasPolicyEnabled(QCall::AppDomainHandle adhTarget); + + static + BOOL QCALLTYPE IsLegacyCasPolicyEnabled(QCall::AppDomainHandle adhTarget); +#endif // FEATURE_CAS_POLICY + +#ifdef FEATURE_APTCA + static + void QCALLTYPE SetCanonicalConditionalAptcaList(QCall::AppDomainHandle adhTarget, + LPCWSTR wszCanonicalConditionalAptcaList); +#endif // FEATURE_APTCA + +#ifdef FEATURE_APPDOMAINMANAGER_INITOPTIONS + static + void QCALLTYPE RegisterWithHost(IUnknown *punkAppDomainManager); +#endif // FEATURE_APPDOMAINMANAGER_INITOPTIONS + +#if defined(FEATURE_APPX_BINDER) + static + ICLRPrivBinder * QCALLTYPE CreateDesignerContext(LPCWSTR *rgPaths, UINT cPaths, BOOL fShared); + + static + void QCALLTYPE SetCurrentDesignerContext(BOOL fDesignerContext, ICLRPrivBinder *newContext); +#endif +}; + +#endif diff --git a/src/vm/appdomainstack.cpp b/src/vm/appdomainstack.cpp new file mode 100644 index 0000000000..bb21c8e7bd --- /dev/null +++ b/src/vm/appdomainstack.cpp @@ -0,0 +1,195 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +// + + +// + + +#include "common.h" + +#include "appdomainstack.h" +#include "appdomainstack.inl" +#include "security.h" +#include "securitypolicy.h" +#include "appdomain.inl" +#ifdef FEATURE_REMOTING +#include "crossdomaincalls.h" +#else +#include "callhelpers.h" +#endif + +#ifdef _DEBUG +void AppDomainStack::CheckOverridesAssertCounts() +{ + LIMITED_METHOD_CONTRACT; + DWORD dwAppDomainIndex = 0; + DWORD dwOverrides = 0; + DWORD dwAsserts = 0; + AppDomainStackEntry *pEntry = NULL; + for(dwAppDomainIndex=0;dwAppDomainIndexm_dwOverridesCount; + dwAsserts += pEntry->m_dwAsserts; + } + _ASSERTE(dwOverrides == m_dwOverridesCount); + _ASSERTE(dwAsserts == m_dwAsserts); +} +#endif + +BOOL AppDomainStackEntry::IsFullyTrustedWithNoStackModifiers(void) +{ + LIMITED_METHOD_CONTRACT; + if (m_domainID.m_dwId == INVALID_APPDOMAIN_ID || m_dwOverridesCount != 0 || m_dwAsserts != 0) + return FALSE; + + AppDomainFromIDHolder pDomain(m_domainID, FALSE); + if (pDomain.IsUnloaded()) + return FALSE; + IApplicationSecurityDescriptor *currAppSecDesc = pDomain->GetSecurityDescriptor(); + if (currAppSecDesc == NULL) + return FALSE; + return Security::CheckDomainWideSpecialFlag(currAppSecDesc, 1 << SECURITY_FULL_TRUST); +} +BOOL AppDomainStackEntry::IsHomogeneousWithNoStackModifiers(void) +{ + LIMITED_METHOD_CONTRACT; + if (m_domainID.m_dwId == INVALID_APPDOMAIN_ID || m_dwOverridesCount != 0 || m_dwAsserts != 0) + return FALSE; + + AppDomainFromIDHolder pDomain(m_domainID, FALSE); + if (pDomain.IsUnloaded()) + return FALSE; + IApplicationSecurityDescriptor *currAppSecDesc = pDomain->GetSecurityDescriptor(); + if (currAppSecDesc == NULL) + return FALSE; + return (currAppSecDesc->IsHomogeneous() && !currAppSecDesc->ContainsAnyRefusedPermissions()); +} + +BOOL AppDomainStackEntry::HasFlagsOrFullyTrustedWithNoStackModifiers(DWORD flags) +{ + LIMITED_METHOD_CONTRACT; + if (m_domainID.m_dwId == INVALID_APPDOMAIN_ID || m_dwOverridesCount != 0 || m_dwAsserts != 0) + return FALSE; + + AppDomainFromIDHolder pDomain(m_domainID, FALSE); + if (pDomain.IsUnloaded()) + return FALSE; + IApplicationSecurityDescriptor *currAppSecDesc = pDomain->GetSecurityDescriptor(); + if (currAppSecDesc == NULL) + return FALSE; + + // either the desired flag (often 0) or fully trusted will do + flags |= (1<GetSecurityDescriptor(); + + if (thisAppSecDesc->IsHomogeneous()) + { + // update the intersection with the current grant set + + NewArrayHolder pbtmpSerializedObject(NULL); + + struct gc + { + OBJECTREF refGrantSet; + } gc; + ZeroMemory( &gc, sizeof( gc ) ); + AppDomain* pCurrentDomain; + pCurrentDomain = GetAppDomain(); + + GCPROTECT_BEGIN( gc ); +#ifdef FEATURE_REMOTING // should not be possible without remoting + DWORD cbtmpSerializedObject = 0; + if (pCurrentDomain->GetId() != m_domainID) + { + // Unlikely scenario where we have another homogeneous AD on the callstack that's different from + // the current one. If there's another AD on the callstack, it's likely to be FT. + ENTER_DOMAIN_ID(m_domainID) + { + // Release the holder to allow GCs. This is safe because we've entered the AD, so it won't go away. + domain.Release(); + + gc.refGrantSet = thisAppSecDesc->GetGrantedPermissionSet(NULL); + AppDomainHelper::MarshalObject(GetAppDomain(), &gc.refGrantSet, &pbtmpSerializedObject, &cbtmpSerializedObject); + if (pbtmpSerializedObject == NULL) + { + // this is an error: possibly an OOM prevented the blob from getting created. + // We could return null and let the managed code use a fully restricted object or throw here. + // Let's throw here... + COMPlusThrow(kSecurityException); + } + gc.refGrantSet = NULL; + + } + END_DOMAIN_TRANSITION + AppDomainHelper::UnmarshalObject(pCurrentDomain,pbtmpSerializedObject, cbtmpSerializedObject, &gc.refGrantSet); + } + else +#else + _ASSERTE(pCurrentDomain->GetId() == m_domainID); +#endif //!FEATURE_CORECLR + { + // Release the holder to allow GCs. This is safe because we're running in this AD, so it won't go away. + domain.Release(); + gc.refGrantSet = thisAppSecDesc->GetGrantedPermissionSet(NULL); + } + + // At this point gc.refGrantSet has the grantSet of pDomain (thisAppSecDesc) in the current domain. + // We don't care about refused perms since we established there were + // none earlier for this call stack. + // Let's intersect with what we've already got. + + PREPARE_NONVIRTUAL_CALLSITE(METHOD__PERMISSION_LIST_SET__UPDATE); + DECLARE_ARGHOLDER_ARRAY(args, 2); + args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(*homogeneousPLS); // arg 0 + args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(gc.refGrantSet); // arg 1 + CALL_MANAGED_METHOD_NORET(args); + + GCPROTECT_END(); + } +} + + +BOOL AppDomainStack::AllDomainsHomogeneousWithNoStackModifiers() +{ + WRAPPER_NO_CONTRACT; + + // Used primarily by CompressedStack code to decide if a CS has to be constructed + + DWORD dwAppDomainIndex = 0; + + + InitDomainIteration(&dwAppDomainIndex); + while (dwAppDomainIndex != 0) + { + AppDomainStackEntry* pEntry = GetNextDomainEntryOnStack(&dwAppDomainIndex); + _ASSERTE(pEntry != NULL); + + if (!pEntry->IsHomogeneousWithNoStackModifiers() && !pEntry->IsFullyTrustedWithNoStackModifiers()) + return FALSE; + } + + return TRUE; +} + diff --git a/src/vm/appdomainstack.h b/src/vm/appdomainstack.h new file mode 100644 index 0000000000..1390364e47 --- /dev/null +++ b/src/vm/appdomainstack.h @@ -0,0 +1,231 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +// Appdomainstack.h - +// + + +// + + +#ifndef __appdomainstack_h__ +#define __appdomainstack_h__ + +#include "vars.hpp" +#include "util.hpp" + + +// Stack of AppDomains executing on the current thread. Used in security optimization to avoid stackwalks +#define ADSTACK_BLOCK_SIZE 16 +#define INVALID_APPDOMAIN_ID ((DWORD)-1) +#define CURRENT_APPDOMAIN_ID ((ADID)(DWORD)0) +#define __GetADID(index) ((index) ADSTACK_BLOCK_SIZE) + { + // #blocks to allocate = ceil(numDomains/blocksize) - 1 = ceil ((numdomains - blocksize)/blocksize) = numdomains/blocksize + DWORD numBlocks = m_numEntries/ADSTACK_BLOCK_SIZE; + m_ExtraStackSize = numBlocks*ADSTACK_BLOCK_SIZE; + m_pExtraStack = new AppDomainStackEntry[m_ExtraStackSize]; + memcpy(m_pExtraStack, stack.m_pExtraStack, sizeof(AppDomainStackEntry)*(m_numEntries-ADSTACK_BLOCK_SIZE)); + FillEntries((m_pExtraStack+m_numEntries-ADSTACK_BLOCK_SIZE), (m_ExtraStackSize -(m_numEntries-ADSTACK_BLOCK_SIZE))); + } + } + + ~AppDomainStack() + { + CONTRACTL + { + MODE_ANY; + GC_NOTRIGGER; + NOTHROW; + } CONTRACTL_END; + if (m_pExtraStack != NULL) + delete[] m_pExtraStack; + m_pExtraStack = NULL; + m_ExtraStackSize = 0; + } + + bool operator!= (const AppDomainStack& stack) const + { + return !(*this == stack); + } + + bool operator== (const AppDomainStack& stack) const + { + LIMITED_METHOD_CONTRACT; + if (this == &stack) // degenerate case: comparing with self + return true; + if (this->m_numEntries != stack.m_numEntries || + this->m_dwAsserts != stack.m_dwAsserts || + this->m_dwOverridesCount != stack.m_dwOverridesCount) + return false; + for (unsigned i =0; i < stack.m_numEntries; i++) + { + if (i < ADSTACK_BLOCK_SIZE) + { + if (this->m_pStack[i] != stack.m_pStack[i]) + return false; + } + else + { + if (this->m_pExtraStack[i-ADSTACK_BLOCK_SIZE] != stack.m_pExtraStack[i-ADSTACK_BLOCK_SIZE]) + return false; + } + } + return true; + } + inline AppDomainStack& operator =(const AppDomainStack& stack) + { + CONTRACTL { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // Degenerate case (assigning x = x) + if (this == &stack) + return *this; + + m_dwThreadWideSpecialFlags = stack.m_dwThreadWideSpecialFlags; + m_numEntries = stack.m_numEntries; + m_dwOverridesCount = stack.m_dwOverridesCount; + m_dwAsserts = stack.m_dwAsserts; + LOG((LF_APPDOMAIN, LL_INFO100, "= operator : m_dwAsserts:%d stack.m_dwAsserts:%d\n",m_dwAsserts, stack.m_dwAsserts)); + memcpy(m_pStack, stack.m_pStack, sizeof( AppDomainStackEntry) * ADSTACK_BLOCK_SIZE); + // If there is anything stored in the extra allocated space, copy that over + if (m_numEntries > ADSTACK_BLOCK_SIZE) + { + // #blocks to allocate = ceil(numDomains/blocksize) - 1 = ceil ((numdomains - blocksize)/blocksize) = numdomains/blocksize + DWORD numBlocks = m_numEntries/ADSTACK_BLOCK_SIZE; + if (m_ExtraStackSize < numBlocks*ADSTACK_BLOCK_SIZE) + { + // free ptr if it exists + if (m_pExtraStack != NULL) + delete[] m_pExtraStack; + m_pExtraStack = NULL; + + m_ExtraStackSize = numBlocks*ADSTACK_BLOCK_SIZE; + m_pExtraStack = new AppDomainStackEntry[m_ExtraStackSize]; + } + + memset(m_pExtraStack, 0xFF, sizeof(ADID) * numBlocks); + memcpy(m_pExtraStack, stack.m_pExtraStack, sizeof(AppDomainStackEntry)*(m_numEntries-ADSTACK_BLOCK_SIZE)); + FillEntries((m_pExtraStack+m_numEntries-ADSTACK_BLOCK_SIZE), (m_ExtraStackSize -(m_numEntries-ADSTACK_BLOCK_SIZE))); + } + + return *this; + } + + inline void PushDomain(ADID pDomain); + inline ADID PopDomain(); + + inline void InitDomainIteration(DWORD *pIndex) const; + // Gets the next AD on the stack + inline ADID GetNextDomainOnStack(DWORD *pIndex, DWORD *pOverrides, DWORD *pAsserts) const; + inline AppDomainStackEntry* GetNextDomainEntryOnStack(DWORD *pIndex); + inline AppDomainStackEntry* GetCurrentDomainEntryOnStack(DWORD pIndex); + // Updates the asserts/overrides on the next AD on the stack + inline void UpdateDomainOnStack(DWORD pIndex, DWORD asserts, DWORD overrides); + inline DWORD GetNumDomains() const; + inline void ClearDomainStack(); + inline DWORD GetThreadWideSpecialFlag() const; + inline DWORD IncrementOverridesCount(); + inline DWORD DecrementOverridesCount(); + inline DWORD GetOverridesCount(); + inline DWORD GetInnerAppDomainOverridesCount(); + inline DWORD IncrementAssertCount(); + inline DWORD DecrementAssertCount(); + inline DWORD GetAssertCount(); + inline DWORD GetInnerAppDomainAssertCount(); + bool IsDefaultSecurityInfo() const; + BOOL AllDomainsHomogeneousWithNoStackModifiers(); + +private: + inline void AddMoreDomains(void); + inline AppDomainStackEntry* ReadTopOfStack(); + void UpdateStackFromEntries(); + static void FillEntries(AppDomainStackEntry ptr[], DWORD size) + { + CONTRACTL + { + MODE_ANY; + GC_NOTRIGGER; + NOTHROW; + }CONTRACTL_END; + _ASSERTE(ptr != NULL); + DWORD i; + const AppDomainStackEntry tmp_entry = {ADID(INVALID_APPDOMAIN_ID), 0, 0}; + for(i=0;i= 0; i--) { + AppDomainStackEntry* pEntry = __GetEntryPtr(i); + + LOG((LF_APPDOMAIN, LL_INFO100, " stack[%d]: AppDomain id[%d] Overrides[%d] Asserts[%d] \n", i, + pEntry->m_domainID.m_dwId, pEntry->m_dwOverridesCount, pEntry->m_dwAsserts)); + } +} + +#else +#define LogADStackUpdateIfDebug +#endif + +inline void AppDomainStack::AddMoreDomains(void) +{ + CONTRACTL { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + // Need to allocate a bigger block for pMoreDomains + AppDomainStackEntry *tmp = m_pExtraStack; + m_pExtraStack = new AppDomainStackEntry[m_ExtraStackSize + ADSTACK_BLOCK_SIZE]; + memcpy(m_pExtraStack, tmp, sizeof(AppDomainStackEntry)*(m_ExtraStackSize)); + FillEntries((m_pExtraStack+m_ExtraStackSize), ADSTACK_BLOCK_SIZE); + m_ExtraStackSize+= ADSTACK_BLOCK_SIZE; + delete[] tmp; // free the old block + +} +inline void AppDomainStack::PushDomain(ADID pDomain) +{ + CONTRACTL { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + LOG((LF_APPDOMAIN, LL_INFO100, "Thread::PushDomain (%d), count now %d\n", pDomain.m_dwId, m_numEntries+1)); + + // + // When entering a new AppDomain, we need to update the thread wide + // state with the intersection of the current and the new AppDomains flags. + // This is because the old AppDomain could have loaded new assemblies + // that are not yet reflected in the thread wide state, and the thread + // could then execute code in that new Assembly. + // We save the old thread wide state in the AppDomainStackEntry so we + // can restore it when we pop the stack entry. + // + + // The pushed domain could be the default AppDomain (which is the starting + // AppDomain for all threads), in which case we don't need to intersect + // with the flags from the previous AppDomain. + Thread* pThread = GetThread(); + if (pThread) + m_dwThreadWideSpecialFlags &= pThread->GetDomain()->GetSecurityDescriptor()->GetDomainWideSpecialFlag(); + + if (m_numEntries == ADSTACK_BLOCK_SIZE + m_ExtraStackSize) + { + AddMoreDomains(); + } + + _ASSERTE(m_numEntries < ADSTACK_BLOCK_SIZE + m_ExtraStackSize); + if (m_numEntries < ADSTACK_BLOCK_SIZE) + { + m_pStack[m_numEntries].m_domainID = pDomain; + m_pStack[m_numEntries].m_dwAsserts = 0; + m_pStack[m_numEntries].m_dwOverridesCount = 0; + m_pStack[m_numEntries].m_dwPreviousThreadWideSpecialFlags = m_dwThreadWideSpecialFlags; + } + else + { + m_pExtraStack[m_numEntries-ADSTACK_BLOCK_SIZE].m_domainID = pDomain ; + m_pExtraStack[m_numEntries-ADSTACK_BLOCK_SIZE].m_dwAsserts = 0; + m_pExtraStack[m_numEntries-ADSTACK_BLOCK_SIZE].m_dwOverridesCount = 0; + m_pExtraStack[m_numEntries-ADSTACK_BLOCK_SIZE].m_dwPreviousThreadWideSpecialFlags = m_dwThreadWideSpecialFlags; + } + + if (pThread) { + AppDomainFromIDHolder pAppDomain(pDomain, TRUE); + if (!pAppDomain.IsUnloaded()) + m_dwThreadWideSpecialFlags &= pAppDomain->GetSecurityDescriptor()->GetDomainWideSpecialFlag(); + } + + m_numEntries++; + + LogADStackUpdateIfDebug; +} + +inline ADID AppDomainStack::PopDomain() +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + ADID pRet = (ADID)INVALID_APPDOMAIN_ID; + _ASSERTE(m_numEntries > 0); + if (m_numEntries > 0) + { + m_numEntries--; + AppDomainStackEntry ret_entry; + const AppDomainStackEntry reset_entry = {ADID(INVALID_APPDOMAIN_ID), 0, 0}; + + if (m_numEntries < ADSTACK_BLOCK_SIZE) + { + ret_entry = m_pStack[m_numEntries]; + m_pStack[m_numEntries] = reset_entry; + } + else + { + ret_entry = m_pExtraStack[m_numEntries-ADSTACK_BLOCK_SIZE]; + m_pExtraStack[m_numEntries-ADSTACK_BLOCK_SIZE] = reset_entry; + } + pRet=ret_entry.m_domainID; + + LOG((LF_APPDOMAIN, LL_INFO100, "PopDomain: Popping pRet.m_dwId [%d] m_dwAsserts:%d ret_entry.m_dwAsserts:%d. New m_dwAsserts:%d\n", + pRet.m_dwId, m_dwAsserts,ret_entry.m_dwAsserts, (m_dwAsserts-ret_entry.m_dwAsserts))); + + m_dwAsserts -= ret_entry.m_dwAsserts; + m_dwOverridesCount -= ret_entry.m_dwOverridesCount; +#ifdef _DEBUG + CheckOverridesAssertCounts(); +#endif + + // + // When leaving an AppDomain, we need to update the thread wide state by + // restoring to the state we were in before entering the AppDomain + // + + m_dwThreadWideSpecialFlags = ret_entry.m_dwPreviousThreadWideSpecialFlags; + + LOG((LF_APPDOMAIN, LL_INFO100, "Thread::PopDomain popping [%d] count now %d\n", + pRet.m_dwId , m_numEntries)); + } + else + { + LOG((LF_APPDOMAIN, LL_INFO100, "Thread::PopDomain count now %d (error pop)\n", m_numEntries)); + } + + LogADStackUpdateIfDebug; + return pRet; +} +#endif // DACCESS_COMPILE + +inline DWORD AppDomainStack::GetNumDomains() const +{ + LIMITED_METHOD_CONTRACT; + _ASSERTE(m_numEntries >= 1); + return m_numEntries; +} + +inline DWORD AppDomainStack::GetThreadWideSpecialFlag() const +{ + LIMITED_METHOD_CONTRACT; + return m_dwThreadWideSpecialFlags; +} + +inline DWORD AppDomainStack::IncrementOverridesCount() +{ + + CONTRACTL + { + MODE_ANY; + GC_NOTRIGGER; + NOTHROW; + SO_TOLERANT;// Yes, we update global state here, but at worst we have an incorrect overrides count that will be updated the next + }CONTRACTL_END; // time we run any code that leads to UpdateOverrides. And I don't see even how that can happen: it doesn't look possible + // for use to take an SO between the update and when we return to managed code. + AppDomainStackEntry *pEntry = ReadTopOfStack(); + _ASSERTE(pEntry->m_domainID.m_dwId != INVALID_APPDOMAIN_ID); + ++(pEntry->m_dwOverridesCount); + return ++m_dwOverridesCount; +} +inline DWORD AppDomainStack::DecrementOverridesCount() +{ + CONTRACTL + { + MODE_ANY; + GC_NOTRIGGER; + NOTHROW; + SO_TOLERANT; + }CONTRACTL_END; + AppDomainStackEntry *pEntry = ReadTopOfStack(); + _ASSERTE(pEntry->m_domainID.m_dwId != INVALID_APPDOMAIN_ID); + _ASSERTE(pEntry->m_dwOverridesCount > 0); + _ASSERTE(m_dwOverridesCount > 0); + if (pEntry->m_dwOverridesCount > 0 && m_dwOverridesCount > 0) + { + --(pEntry->m_dwOverridesCount); + return --m_dwOverridesCount; + } + + return 0; +} +inline DWORD AppDomainStack::GetOverridesCount() +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + } + CONTRACTL_END; +#ifdef _DEBUG + CheckOverridesAssertCounts(); +#endif + return m_dwOverridesCount; +} + +inline DWORD AppDomainStack::GetInnerAppDomainOverridesCount() +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + } + CONTRACTL_END; +#ifdef _DEBUG + CheckOverridesAssertCounts(); +#endif + AppDomainStackEntry *pEntry = ReadTopOfStack(); + _ASSERTE(pEntry->m_domainID.m_dwId != INVALID_APPDOMAIN_ID); + + return pEntry->m_dwOverridesCount; +} + +inline DWORD AppDomainStack::IncrementAssertCount() +{ + LIMITED_METHOD_CONTRACT; + AppDomainStackEntry *pEntry = ReadTopOfStack(); + _ASSERTE(pEntry->m_domainID.m_dwId != INVALID_APPDOMAIN_ID); + LOG((LF_APPDOMAIN, LL_INFO100, "IncrementAssertCount: m_dwAsserts:%d ADID:%d pEntry:%p pEntry->m_dwAsserts:%d.\n", + m_dwAsserts, pEntry->m_domainID.m_dwId, pEntry, pEntry->m_dwAsserts)); + ++(pEntry->m_dwAsserts); + return ++m_dwAsserts; +} +inline DWORD AppDomainStack::DecrementAssertCount() +{ + LIMITED_METHOD_CONTRACT; + AppDomainStackEntry *pEntry = ReadTopOfStack(); + _ASSERTE(pEntry->m_domainID.m_dwId != INVALID_APPDOMAIN_ID); + _ASSERTE(pEntry->m_dwAsserts > 0); + _ASSERTE(m_dwAsserts > 0); + LOG((LF_APPDOMAIN, LL_INFO100, "DecrementAssertCount: m_dwAsserts:%d ADID:%d pEntry:%p pEntry->m_dwAsserts:%d.\n", + m_dwAsserts, pEntry->m_domainID.m_dwId, pEntry, pEntry->m_dwAsserts)); + --(pEntry->m_dwAsserts); + return --m_dwAsserts; +} + +inline DWORD AppDomainStack::GetAssertCount() +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + } + CONTRACTL_END; +#ifdef _DEBUG + CheckOverridesAssertCounts(); +#endif + + return m_dwAsserts; +} + +inline DWORD AppDomainStack::GetInnerAppDomainAssertCount() +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + } + CONTRACTL_END; +#ifdef _DEBUG + CheckOverridesAssertCounts(); +#endif + AppDomainStackEntry *pEntry = ReadTopOfStack(); + _ASSERTE(pEntry->m_domainID.m_dwId != INVALID_APPDOMAIN_ID); + + return pEntry->m_dwAsserts; +} + +inline void AppDomainStack::InitDomainIteration(DWORD *pIndex) const +{ + LIMITED_METHOD_CONTRACT; + *pIndex = m_numEntries; +} + +inline ADID AppDomainStack::GetNextDomainOnStack(DWORD *pIndex, DWORD *pOverrides, DWORD *pAsserts) const +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + } + CONTRACTL_END; + + _ASSERTE(*pIndex > 0 && *pIndex <= m_numEntries); + (*pIndex) --; + const AppDomainStackEntry *pEntry = __GetEntryPtr(*pIndex); + if (pOverrides != NULL) + *pOverrides = pEntry->m_dwOverridesCount; + if (pAsserts != NULL) + *pAsserts = pEntry->m_dwAsserts; + return (ADID)pEntry->m_domainID.m_dwId; +} + +inline AppDomainStackEntry* AppDomainStack::GetCurrentDomainEntryOnStack(DWORD pIndex) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + _ASSERTE(pIndex >=0 && pIndex < m_numEntries); + return __GetEntryPtr(pIndex); +} + +inline AppDomainStackEntry* AppDomainStack::GetNextDomainEntryOnStack(DWORD *pIndex) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + } + CONTRACTL_END; + + _ASSERTE(*pIndex >0 && *pIndex <= m_numEntries); + (*pIndex) --; + return __GetEntryPtr(*pIndex); +} + +inline void AppDomainStack::UpdateDomainOnStack(DWORD pIndex, DWORD asserts, DWORD overrides) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + AppDomainStackEntry* entry; + _ASSERTE(pIndex >=0 && pIndex < m_numEntries); + entry = __GetEntryPtr(pIndex); + _ASSERTE(entry->m_domainID.m_dwId != INVALID_APPDOMAIN_ID); + entry->m_dwAsserts = asserts; + entry->m_dwOverridesCount = overrides; + UpdateStackFromEntries(); + +} + + +inline void AppDomainStack::UpdateStackFromEntries() +{ + LIMITED_METHOD_CONTRACT; + DWORD dwAppDomainIndex = 0; + DWORD dwOverrides = 0; + DWORD dwAsserts = 0; + AppDomainStackEntry *pEntry = NULL; + for(dwAppDomainIndex=0;dwAppDomainIndexm_dwOverridesCount; + dwAsserts += pEntry->m_dwAsserts; + } + LOG((LF_APPDOMAIN, LL_INFO100, "UpdateStackFromEntries: m_dwAsserts:%d Calculated dwAsserts:%d.\n",m_dwAsserts,dwAsserts)); + + m_dwAsserts = dwAsserts; + m_dwOverridesCount = dwOverrides; + return; +} + +inline AppDomainStackEntry* AppDomainStack::ReadTopOfStack() +{ + LIMITED_METHOD_CONTRACT; + _ASSERTE(m_numEntries > 0); + AppDomainStackEntry* pEntry = NULL; + if (m_numEntries <= ADSTACK_BLOCK_SIZE) + { + pEntry = &(m_pStack[m_numEntries-1]); + } + else + { + pEntry = &(m_pExtraStack[m_numEntries-ADSTACK_BLOCK_SIZE-1]); + } + return pEntry; +} + +inline bool AppDomainStack::IsDefaultSecurityInfo() const +{ + LIMITED_METHOD_CONTRACT; + return (m_numEntries == 1 && m_pStack[0].m_domainID == ADID(DefaultADID) && + m_pStack[0].m_dwAsserts == 0 && m_pStack[0].m_dwOverridesCount == 0); +} +inline void AppDomainStack::ClearDomainStack() +{ + CONTRACTL + { + MODE_ANY; + GC_NOTRIGGER; + NOTHROW; + }CONTRACTL_END; + m_dwThreadWideSpecialFlags = 0xFFFFFFFF; + m_numEntries = 1; + FillEntries(m_pStack, ADSTACK_BLOCK_SIZE); + if (m_pExtraStack != NULL) + delete[] m_pExtraStack; + m_pExtraStack = NULL; + m_ExtraStackSize = 0; + m_dwOverridesCount = 0; + LOG((LF_APPDOMAIN, LL_INFO100, "ClearDomainStack: m_dwAsserts:%d setting to 0\n",m_dwAsserts)); + m_dwAsserts = 0; + m_pStack[0].m_domainID = ADID(DefaultADID); +} + +#endif diff --git a/src/vm/appxutil.cpp b/src/vm/appxutil.cpp new file mode 100644 index 0000000000..6bc04d74cd --- /dev/null +++ b/src/vm/appxutil.cpp @@ -0,0 +1,242 @@ +// 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. +// +// +// Provides VM-specific AppX utility code. + +#include "common.h" + +#include "utilcode.h" +#include "holder.h" +#include "volatile.h" +#include "appxutil.h" +#include "ex.h" + +#include "Windows.ApplicationModel.h" +#include "Windows.ApplicationModel.Core.h" + +namespace AppX +{ + //----------------------------------------------------------------------------------- + // This is a small helper class designed to ensure that the current thread is + // RoInitialized for the lifetime of the holder. Use this holder only if code does + // not store any WinRT interfaces in locations that will out-live the holder + // itself. + + class RoInitializeHolder + { + public: + enum ThreadingModel + { + MultiThreaded, // Require multi-threaded model + SingleThreaded, // Require single-threaded model + AnyThreadedMultiPreferred // Any threading model is ok; + // prefer multi-threaded model + }; + + RoInitializeHolder( + ThreadingModel threadingModel) // desired/preferred apartment model + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END + + HRESULT hr = S_OK; + + { + GCX_PREEMP(); + LeaveRuntimeHolder lrh(::RoInitialize); + + // Prefer MultiThreaded when AnyThreadedMultiPreferred is specified. + hr = ::RoInitialize((threadingModel == SingleThreaded) ? RO_INIT_SINGLETHREADED + : RO_INIT_MULTITHREADED); + } + + // Success means that the thread's RoInitialize ref count has been incremented, + // and must be paired with a call to RoUnintialize. + _uninitRequired = SUCCEEDED(hr); + + if (FAILED(hr)) + { + // Throw if: + // 1. RoInitialize failed for any reason other than RPC_E_CHANGED_MODE + // 2. RoInitialize failed with RPC_E_CHANGED_MODE and caller will not + // accept a different apartment model. + if (hr != RPC_E_CHANGED_MODE || threadingModel != AnyThreadedMultiPreferred) + { + // Note: throwing here will cause us to skip the dtor, but will only + // do so when SUCCEEDED(hr) is FALSE, which means that _uninitRequired + // is also FALSE so there is no RoInitialize refcount leak here. + _ASSERTE(!_uninitRequired); + + ThrowHR(hr); + } + } + } + + // Ensures RoUninitialize is called (if needed) before holder falls out of scope. + ~RoInitializeHolder() + { + LIMITED_METHOD_CONTRACT; + if (_uninitRequired) + { + _uninitRequired = false; + ::RoUninitialize(); + } + } + + private: + bool _uninitRequired; // Is a call to RoUnitialize required? + }; + + //----------------------------------------------------------------------------------- + + HRESULT IsAppXDesignModeWorker(bool * pfResult) + { + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END + + HRESULT hr = S_OK; + + boolean fDesignModeEnabled = false; + + // Delayloaded entrypoint may throw. + EX_TRY + { + // Ensure that thread is initialized for WinRT; either apt model will work for this API. + RoInitializeHolder hRoInit(RoInitializeHolder::AnyThreadedMultiPreferred); + + ReleaseHolder pIDesignMode; + IfFailThrow(clr::winrt::GetActivationFactory( + RuntimeClass_Windows_ApplicationModel_DesignMode, pIDesignMode)); + + IfFailThrow(pIDesignMode->get_DesignModeEnabled(&fDesignModeEnabled)); + } + EX_CATCH_HRESULT(hr) + IfFailRet(hr); + + if (!!fDesignModeEnabled) + { + *pfResult = true; + return S_OK; + } + + *pfResult = false; + return S_OK; + } + + //----------------------------------------------------------------------------------- + // Returns true if running in an AppX process with DevMode enabled. + + bool IsAppXDesignMode() + { + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END + +#ifdef FEATURE_CORECLR + // CoreCLR does not have proper support for AppX design mode. Once/if it has one, it should not need + // any special casing like desktop. Avoid the expensive check completely. + return false; +#else + // DevMode does not change over the lifetime of a process and is expensive to compute + // Cache the first answer and return it once computed; idempotent so races are fine + static enum + { + CachedAppxMode_Unknown, + CachedAppxMode_Normal, + CachedAppxMode_Design + } + s_cachedAppxMode = CachedAppxMode_Unknown; + + bool result = false; + + switch (s_cachedAppxMode) + { + case CachedAppxMode_Unknown: + if (SUCCEEDED(IsAppXDesignModeWorker(&result))) + { // Cache the result on success; otherwise use the default value of false. + s_cachedAppxMode = result ? CachedAppxMode_Design : CachedAppxMode_Normal; + } + break; + + case CachedAppxMode_Normal: + result = false; + break; + + case CachedAppxMode_Design: + result = true; + break; + } + +#ifdef _DEBUG + bool dbg_result = false; + _ASSERTE(FAILED(IsAppXDesignModeWorker(&dbg_result)) || dbg_result == result); +#endif + + return result; +#endif // FEATURE_CORECLR + } + + HRESULT GetApplicationId(LPCWSTR& rString) + { + LIMITED_METHOD_CONTRACT; + + // the PRAID is a static value for the life of the process. the reason for caching is + // because the watson bucketing code requires this value during unhandled exception + // processing and due to the contracts in that code it cannot tolerate the switch to + // preemptive mode when calling out to WinRT. + static LPCWSTR s_wzPraid = nullptr; + + HRESULT hr = S_OK; + + if (s_wzPraid == nullptr) + { + ReleaseHolder coreApp; + + hr = clr::winrt::GetActivationFactory(RuntimeClass_Windows_ApplicationModel_Core_CoreApplication, coreApp); + + if (SUCCEEDED(hr)) + { + WinRtString winrtAppId; + hr = coreApp->get_Id(winrtAppId.Address()); + + if (SUCCEEDED(hr)) + { + LPCWSTR wzPraid = DuplicateString(winrtAppId.GetRawBuffer(), winrtAppId.size()); + if (wzPraid) + { + if (InterlockedCompareExchangeT(&s_wzPraid, wzPraid, nullptr) != nullptr) + delete[] wzPraid; + } + else + { + hr = E_OUTOFMEMORY; + } + } + } + } + + rString = s_wzPraid; + + return hr; + } + + +} + + diff --git a/src/vm/appxutil.h b/src/vm/appxutil.h new file mode 100644 index 0000000000..1e18fcce04 --- /dev/null +++ b/src/vm/appxutil.h @@ -0,0 +1,31 @@ +// 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. +// + +// +// Provides VM-specific AppX utility code. + +#ifndef vm_AppXUtil_h +#define vm_AppXUtil_h + +#include "../inc/appxutil.h" + +namespace AppX +{ +#if defined(FEATURE_APPX) && !defined(CROSSGEN_COMPILE) + //----------------------------------------------------------------------------------- + // Returns true if running in an AppX process with Designer Mode enabled. + bool IsAppXDesignMode(); + + // Return Application.Id + HRESULT GetApplicationId(LPCWSTR& rString); +#else // FEATURE_APPX + inline bool IsAppXDesignMode() + { + return false; + } +#endif // FEATURE_APPX && !CROSSGEN_COMPILE +} + +#endif // vm_AppXUtil_h diff --git a/src/vm/aptca.cpp b/src/vm/aptca.cpp new file mode 100644 index 0000000000..e4e8f19255 --- /dev/null +++ b/src/vm/aptca.cpp @@ -0,0 +1,1363 @@ +// 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. +//-------------------------------------------------------------------------- +// aptca.h +// +// Functions for handling allow partially trusted callers assemblies +// + +// +//-------------------------------------------------------------------------- + + +#include "common.h" +#include "aptca.h" + +// +// Conditional APTCA cache implementation +// + +ConditionalAptcaCache::ConditionalAptcaCache(AppDomain *pAppDomain) + : m_pAppDomain(pAppDomain), + m_canonicalListIsNull(false), + m_domainState(kDomainStateUnknown) +{ + WRAPPER_NO_CONTRACT; + + _ASSERTE(pAppDomain != NULL); +} + +ConditionalAptcaCache::~ConditionalAptcaCache() +{ + WRAPPER_NO_CONTRACT; +} + +void ConditionalAptcaCache::SetCachedState(PTR_PEImage pImage, ConditionalAptcaCache::State state) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(CheckPointer(pImage)); + PRECONDITION(state != kUnknown); + } + CONTRACTL_END; + + if (state == kNotCAptca) + { + pImage->SetIsNotConditionalAptca(); + } +} + +ConditionalAptcaCache::State ConditionalAptcaCache::GetCachedState(PTR_PEImage pImage) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(CheckPointer(pImage)); + } + CONTRACTL_END; + + if (!pImage->MayBeConditionalAptca()) + { + return kNotCAptca; + } + + return kUnknown; +} + +void ConditionalAptcaCache::SetCanonicalConditionalAptcaList(LPCWSTR wszCanonicalConditionalAptcaList) +{ + WRAPPER_NO_CONTRACT; + m_canonicalListIsNull = (wszCanonicalConditionalAptcaList == NULL); + m_canonicalList.Set(wszCanonicalConditionalAptcaList); +} + +#ifndef CROSSGEN_COMPILE +ConditionalAptcaCache::DomainState ConditionalAptcaCache::GetConditionalAptcaDomainState() +{ + CONTRACTL + { + GC_TRIGGERS; + THROWS; + MODE_ANY; + } + CONTRACTL_END; + + if (m_domainState == kDomainStateUnknown) + { + IApplicationSecurityDescriptor *pASD = m_pAppDomain->GetSecurityDescriptor(); + DomainState domainState = kDomainStateUnknown; + + // In the full trust case we only need to look at the conditional APTCA list in the case that the host + // has configured one on the default domain (for instance WPF). Otherwise, all full trust domains have + // all conditional APTCA assemblies enabled. + bool processFullTrustAptcaList = false; + if (m_pAppDomain->IsCompilationDomain()) + { + processFullTrustAptcaList = false; + } + else if (m_pAppDomain->IsDefaultDomain()) + { + processFullTrustAptcaList = !m_canonicalListIsNull; + } + else + { + processFullTrustAptcaList = ConsiderFullTrustConditionalAptcaLists(); + } + + // Consider the domain to be fully trusted if it really is fully trusted, or if we're currently + // setting the domain up, it looks like it will be fully trusted, and the AppDomainManager has + // promised that won't change. + bool isFullTrustDomain = !m_pAppDomain->GetSecurityDescriptor()->DomainMayContainPartialTrustCode(); + if (pASD->IsInitializationInProgress() && (m_pAppDomain->GetAppDomainManagerInitializeNewDomainFlags() & eInitializeNewDomainFlags_NoSecurityChanges)) + { + BOOL preResolveFullTrust; + BOOL preResolveHomogenous; + pASD->PreResolve(&preResolveFullTrust, &preResolveHomogenous); + + isFullTrustDomain = preResolveFullTrust && preResolveHomogenous; + } + + if (m_pAppDomain->IsCompilationDomain()) + { + // NGEN always enables all conditional APTCA assemblies + domainState = kAllEnabled; + } + else if (!isFullTrustDomain || processFullTrustAptcaList) + { + if (m_canonicalList.GetCount() == 0) + { + // A null or empty conditional APTCA list means that no assemblies are enabled in this domain + domainState = kAllDisabled; + } + else + { + // We're in a domain that supports conditional APTCA and an interesting list is supplied. In + // this domain, some assemblies are enabled. + domainState = kSomeEnabled; + } + } + else + { + domainState = kAllEnabled; + } + + _ASSERTE(domainState != kDomainStateUnknown); + InterlockedCompareExchange(reinterpret_cast(&m_domainState), domainState, kDomainStateUnknown); + } + + return m_domainState; +} + +// static +bool ConditionalAptcaCache::ConsiderFullTrustConditionalAptcaLists() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + if (GetAppDomain()->IsCompilationDomain()) + { + return false; + } + + IApplicationSecurityDescriptor *pASD = SystemDomain::System()->DefaultDomain()->GetSecurityDescriptor(); + ConditionalAptcaCache *pDefaultDomainCaptca = pASD->GetConditionalAptcaCache(); + + // The only way that we use CAPTCA lists is if the host has configured the default domain to not be all + // enabled (that is, the host has setup a CAPTCA list of any sort for the default domain) + return pDefaultDomainCaptca->GetConditionalAptcaDomainState() != kAllEnabled; +} + +// APTCA killbit list helper functions +namespace +{ + static const LPCWSTR wszAptcaRootKey = W("SOFTWARE\\Microsoft\\.NETFramework\\Policy\\APTCA"); + + //-------------------------------------------------------------------------------------------------------- + // + // The AptcaKillBitList class is responsible for holding the machine wide list of assembly name / file + // versions which have been disabled for APTCA on the machine. + // + + class AptcaKillBitList + { + private: + ArrayList m_killBitList; + + public: + ~AptcaKillBitList(); + + bool AreAnyAssembliesKillBitted(); + bool IsAssemblyKillBitted(PEAssembly *pAssembly); + bool IsAssemblyKillBitted(IAssemblyName *pAssemblyName, ULARGE_INTEGER fileVersion); + + static AptcaKillBitList *ReadMachineKillBitList(); + + private: + AptcaKillBitList(); + AptcaKillBitList(const AptcaKillBitList &other); // not implemented + + private: + static const LPCWSTR wszKillBitValue; + + private: + static bool FileVersionsAreEqual(ULARGE_INTEGER targetVersion, IAssemblyName *pKillBitAssemblyName); + }; + const LPCWSTR AptcaKillBitList::wszKillBitValue = W("APTCA_FLAG"); + + AptcaKillBitList::AptcaKillBitList() + { + LIMITED_METHOD_CONTRACT; + } + + AptcaKillBitList::~AptcaKillBitList() + { + WRAPPER_NO_CONTRACT; + + // Release all of the IAssemblyName objects stored in this list + for (DWORD i = 0; i < m_killBitList.GetCount(); ++i) + { + IAssemblyName *pKillBitAssemblyName = reinterpret_cast(m_killBitList.Get(i)); + if (pKillBitAssemblyName != NULL) + { + pKillBitAssemblyName->Release(); + } + } + } + + //-------------------------------------------------------------------------------------------------------- + // + // Determine if any assemblies are on the APTCA killbit list + // + + bool AptcaKillBitList::AreAnyAssembliesKillBitted() + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + // We don't consider the killbit for NGEN, as ngened code always assumes that APTCA is enabled. + if (GetAppDomain()->IsCompilationDomain()) + { + return false; + } + + return m_killBitList.GetCount() > 0; + } + + //-------------------------------------------------------------------------------------------------------- + // + // Compare the file versions of an assembly with the verison that is being killbitted to see if they + // match. For compatibility with v3.5, we assume any failure means that the versions do not match. + // + + // static + bool AptcaKillBitList::FileVersionsAreEqual(ULARGE_INTEGER targetVersion, IAssemblyName *pKillBitAssemblyName) + { + DWORD dwKillBitMajorVersion = 0; + DWORD dwVersionSize = sizeof(dwKillBitMajorVersion); + if (FAILED(pKillBitAssemblyName->GetProperty(ASM_NAME_FILE_MAJOR_VERSION, &dwKillBitMajorVersion, &dwVersionSize)) || + dwVersionSize == 0) + { + return false; + } + + DWORD dwKillBitMinorVersion = 0; + dwVersionSize = sizeof(dwKillBitMinorVersion); + if (FAILED(pKillBitAssemblyName->GetProperty(ASM_NAME_FILE_MINOR_VERSION, &dwKillBitMinorVersion, &dwVersionSize)) || + dwVersionSize == 0) + { + return false; + } + + DWORD dwKillBitBuildVersion = 0; + dwVersionSize = sizeof(dwKillBitBuildVersion); + if (FAILED(pKillBitAssemblyName->GetProperty(ASM_NAME_FILE_BUILD_NUMBER, &dwKillBitBuildVersion, &dwVersionSize)) || + dwVersionSize == 0) + { + return false; + } + + DWORD dwKillBitRevisionVersion = 0; + dwVersionSize = sizeof(dwKillBitRevisionVersion); + if (FAILED(pKillBitAssemblyName->GetProperty(ASM_NAME_FILE_REVISION_NUMBER, &dwKillBitRevisionVersion, &dwVersionSize)) || + dwVersionSize == 0) + { + return false; + } + + DWORD dwTargetMajorVersion = (targetVersion.HighPart & 0xFFFF0000) >> 16; + DWORD dwTargetMinorVersion = targetVersion.HighPart & 0x0000FFFF; + DWORD dwTargetBuildVersion = (targetVersion.LowPart & 0xFFFF0000) >> 16; + DWORD dwTargetRevisionVersion = targetVersion.LowPart & 0x0000FFFF; + + return dwTargetMajorVersion == dwKillBitMajorVersion && + dwTargetMinorVersion == dwKillBitMinorVersion && + dwTargetBuildVersion == dwKillBitBuildVersion && + dwTargetRevisionVersion == dwKillBitRevisionVersion; + } + + //-------------------------------------------------------------------------------------------------------- + // + // Determine if a specific assembly is on the killbit list + // + + bool AptcaKillBitList::IsAssemblyKillBitted(PEAssembly *pAssembly) + { + STANDARD_VM_CONTRACT; + + IAssemblyName *pTargetAssemblyName = pAssembly->GetFusionAssemblyName(); + + // For compat with v3.5, we use hte Win32 file version here rather than the Fusion version + LPCWSTR pwszPath = pAssembly->GetPath().GetUnicode(); + if (pwszPath != NULL) + { + ULARGE_INTEGER fileVersion = { 0, 0 }; + HRESULT hr = GetFileVersion(pwszPath, &fileVersion); + if (SUCCEEDED(hr)) + { + return IsAssemblyKillBitted(pTargetAssemblyName, fileVersion); + } + } + + return false; + } + + //-------------------------------------------------------------------------------------------------------- + // + // Determine if a specific assembly is on the killbit list + // + + bool AptcaKillBitList::IsAssemblyKillBitted(IAssemblyName *pTargetAssemblyName, ULARGE_INTEGER fileVersion) + { + STANDARD_VM_CONTRACT; + + // If nothing is killbitted, then this assembly cannot be killbitted + if (!AreAnyAssembliesKillBitted()) + { + return false; + } + + for (DWORD i = 0; i < m_killBitList.GetCount(); ++i) + { + IAssemblyName *pKillBitAssemblyName = reinterpret_cast(m_killBitList.Get(i)); + + // By default, we compare all fields of the assembly's name, however if the culture was neutral, + // we strip that out. + DWORD dwCmpFlags = ASM_CMPF_IL_ALL; + + DWORD cbCultureSize = 0; + SString strCulture; + HRESULT hrCulture = pKillBitAssemblyName->GetProperty(ASM_NAME_CULTURE, NULL, &cbCultureSize); + if (hrCulture == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) + { + DWORD cchCulture = (cbCultureSize / sizeof(WCHAR)) - 1; + WCHAR *wszCultureBuffer = strCulture.OpenUnicodeBuffer(cchCulture); + hrCulture = pKillBitAssemblyName->GetProperty(ASM_NAME_CULTURE, wszCultureBuffer, &cbCultureSize); + strCulture.CloseBuffer(); + } + + if (SUCCEEDED(hrCulture)) + { + if (cbCultureSize == 0 || strCulture.EqualsCaseInsensitive(W("")) || strCulture.EqualsCaseInsensitive(W("neutral"))) + { + dwCmpFlags &= ~ASM_CMPF_CULTURE; + } + } + + // If the input assembly matches the kill bit assembly's name and file version, then we need to + // kill it. + if (pTargetAssemblyName->IsEqual(pKillBitAssemblyName, dwCmpFlags) == S_OK && + FileVersionsAreEqual(fileVersion, pKillBitAssemblyName)) + { + return true; + } + } + + return false; + } + + //-------------------------------------------------------------------------------------------------------- + // + // Read the machine-wide APTCA kill bit list into a kill bit list object. For compatibility with v3.5, + // errors during this initialization are ignored - leading to APTCA entries that may not be considered + // for kill bitting. + // + + // static + AptcaKillBitList *AptcaKillBitList::ReadMachineKillBitList() + { + CONTRACT(AptcaKillBitList *) + { + STANDARD_VM_CHECK; + POSTCONDITION(CheckPointer(RETVAL)); + } + CONTRACT_END; + + NewHolder pKillBitList(new AptcaKillBitList); + + HKEYHolder hKeyAptca; + + // Open the APTCA subkey in the registry. + if (WszRegOpenKeyEx(HKEY_LOCAL_MACHINE, wszAptcaRootKey, 0, KEY_READ, &hKeyAptca) == ERROR_SUCCESS) + { + + DWORD cchSubKeySize = 0; + if (WszRegQueryInfoKey(hKeyAptca, NULL, NULL, NULL, NULL, &cchSubKeySize, NULL, NULL, NULL, NULL, NULL, NULL) != ERROR_SUCCESS) + { + cchSubKeySize = MAX_PATH_FNAME; + } + ++cchSubKeySize; + + NewArrayHolder wszSubKey(new WCHAR[cchSubKeySize]); + + DWORD dwKey = 0; + DWORD cchWszSubKey = cchSubKeySize; + // Assembly specific records are represented as subkeys of the key we've just opened with names + // equal to the strong name of the assembly being kill bitted, and a value of APTCA_FLAG = 1. + while (WszRegEnumKeyEx(hKeyAptca, dwKey, wszSubKey, &cchWszSubKey, NULL, NULL, NULL, NULL) == ERROR_SUCCESS) + { + ++dwKey; + cchWszSubKey = cchSubKeySize; + + // Open the subkey: the key name is the full name of the assembly to potentially kill-bit + HKEYHolder hSubKey; + if (WszRegOpenKeyEx(hKeyAptca, wszSubKey, 0, KEY_READ, &hSubKey) != ERROR_SUCCESS) + { + continue; + } + + DWORD dwKillbit = 0; + DWORD dwType = REG_DWORD; + DWORD dwSize = sizeof(dwKillbit); + + // look for the APTCA flag + LONG queryValue = WszRegQueryValueEx(hSubKey, + wszKillBitValue, + NULL, + &dwType, + reinterpret_cast(&dwKillbit), + &dwSize); + if (queryValue == ERROR_SUCCESS && dwKillbit == 1) + { + // We have a strong named assembly with an APTCA killbit value set - parse the key into + // an assembly name, and add it to our list + ReleaseHolder pKillBitAssemblyName; + HRESULT hrAssemblyName = CreateAssemblyNameObject(&pKillBitAssemblyName, wszSubKey, CANOF_PARSE_DISPLAY_NAME, NULL); + if (FAILED(hrAssemblyName)) + { + continue; + } + + // + // For compatibility with v3.5, we only accept kill bit entries which have four part + // assembly versions, names, and public key tokens. + // + + // Verify the version first + bool validVersion = true; + for (DWORD dwVersionPartId = ASM_NAME_MAJOR_VERSION; dwVersionPartId <= ASM_NAME_REVISION_NUMBER; ++dwVersionPartId) + { + DWORD dwVersionPart; + DWORD cbVersionPart = sizeof(dwVersionPart); + HRESULT hrVersion = pKillBitAssemblyName->GetProperty(dwVersionPartId, &dwVersionPart, &cbVersionPart); + if (FAILED(hrVersion) || cbVersionPart == 0) + { + validVersion = false; + } + } + if (!validVersion) + { + continue; + } + + // Make sure there is a simple name + DWORD cbNameSize = 0; + HRESULT hrName = pKillBitAssemblyName->GetProperty(ASM_NAME_NAME, NULL, &cbNameSize); + if (hrName != HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) + { + continue; + } + + // Verify the killbit assembly has a public key token + DWORD cbPublicKeyTokenSize = 0; + HRESULT hrPublicKey = pKillBitAssemblyName->GetProperty(ASM_NAME_PUBLIC_KEY_TOKEN, NULL, &cbPublicKeyTokenSize); + if (hrPublicKey != HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) + { + continue; + } + + // Verify the killbit assembly has either no culture or a valid culture token + DWORD cbCultureSize = 0; + HRESULT hrCulture = pKillBitAssemblyName->GetProperty(ASM_NAME_CULTURE, NULL, &cbCultureSize); + if (FAILED(hrCulture) && hrCulture != HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) + { + continue; + } + + // The name checks out, so add the kill bit entry + LOG((LF_SECURITY, + LL_INFO10, + "APTCA killbit added for assembly '%S'.\n", + wszSubKey)); + pKillBitList->m_killBitList.Append(pKillBitAssemblyName.Extract()); + } + } + } + + RETURN(pKillBitList.Extract()); + } + + VolatilePtr g_pAptcaKillBitList(NULL); + + //-------------------------------------------------------------------------------------------------------- + // + // Get the APTCA killbit list + // + + AptcaKillBitList *GetKillBitList() + { + STANDARD_VM_CONTRACT; + + if (g_pAptcaKillBitList.Load() == NULL) + { + NewHolder pAptcaKillBitList(AptcaKillBitList::ReadMachineKillBitList()); + + LPVOID pvOldValue = InterlockedCompareExchangeT(g_pAptcaKillBitList.GetPointer(), + pAptcaKillBitList.GetValue(), + NULL); + if (pvOldValue == NULL) + { + pAptcaKillBitList.SuppressRelease(); + } + } + + _ASSERTE(g_pAptcaKillBitList.Load() != NULL); + return g_pAptcaKillBitList.Load(); + } +} + +// APTCA helper functions +namespace +{ + enum ConditionalAptcaSharingMode + { + kShareUnknown, + kShareIfEnabled, // Share an assembly only if all conditional APTCA assemblies in its closure are enabled + kShareIfDisabled, // Share an assembly only if all conditional APTCA assemblies in its closure are disabled + }; + + //-------------------------------------------------------------------------------------------------------- + // + // Get the name of an assembly as it would appear in the APTCA enabled list of an AppDomain + // + + void GetAssemblyNameForConditionalAptca(Assembly *pAssembly, SString *pAssemblyName) + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(CheckPointer(pAssembly)); + PRECONDITION(CheckPointer(pAssemblyName)); + } + CONTRACTL_END; + + GCX_COOP(); + + // Call assembly.GetName().GetNameWithPublicKey() to get the name the user would have to add to the + // whitelist to enable this assembly + struct + { + OBJECTREF orAssembly; + STRINGREF orAssemblyName; + } + gc; + ZeroMemory(&gc, sizeof(gc)); + + GCPROTECT_BEGIN(gc); + + gc.orAssembly = pAssembly->GetExposedObject(); + MethodDescCallSite getAssemblyName(METHOD__ASSEMBLY__GET_NAME_FOR_CONDITIONAL_APTCA, &gc.orAssembly); + ARG_SLOT args[1] = + { + ObjToArgSlot(gc.orAssembly) + }; + gc.orAssemblyName = getAssemblyName.Call_RetSTRINGREF(args); + + // Copy to assemblyName + pAssemblyName->Set(gc.orAssemblyName->GetBuffer()); + + GCPROTECT_END(); + } + + //-------------------------------------------------------------------------------------------------------- + // + // Determine which types of conditional APTCA assemblies may be shared + // + + ConditionalAptcaSharingMode GetConditionalAptcaSharingMode() + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + static ConditionalAptcaSharingMode sharingMode = kShareUnknown; + + if (sharingMode == kShareUnknown) + { + // If the default domain has any conditional APTCA assemblies enabled in it, then we share in the + // enabled direction. Otherwise, the default domain has all conditional APTCA assemblies disabled + // so we need to share in the disabled direction + ConditionalAptcaCache *pDefaultDomainCache = SystemDomain::System()->DefaultDomain()->GetSecurityDescriptor()->GetConditionalAptcaCache(); + ConditionalAptcaCache::DomainState domainState = pDefaultDomainCache->GetConditionalAptcaDomainState(); + + if (domainState == ConditionalAptcaCache::kAllDisabled) + { + sharingMode = kShareIfDisabled; + } + else + { + sharingMode = kShareIfEnabled; + } + } + + return sharingMode; + } + + /* XXX Fri 7/17/2009 + * I can't call DomainAssembly::IsConditionalAPTCAVisible() here. That requires an Assembly which means + * we have to be at FILE_LOAD_ALLOCATE. There are two problems: + * 1) We don't want to load dependencies here if we can avoid it + * 2) We can't load them anyway (hard bound dependencies can't get past + * FILE_LOAD_VERIFY_NATIVE_IMAGE_DEPENDENCIES. + * + * We're going to do a relaxed check here. Instead of checking the public key, we're + * only going to check the public key token. See + * code:AppDomain::IsAssemblyOnAptcaVisibleListRaw for more information. + * + * pAsmName - The name of the assembly to check. + * pDomainAssembly - The Domain Assembly used for logging. + */ + bool IsAssemblyOnAptcaVisibleList(IAssemblyName * pAsmName, DomainAssembly *pDomainAssembly) + { + CONTRACTL + { + STANDARD_VM_CHECK; + PRECONDITION(CheckPointer(pAsmName)); + } + CONTRACTL_END; + + ConditionalAptcaCache *pDomainCache = pDomainAssembly->GetAppDomain()->GetSecurityDescriptor()->GetConditionalAptcaCache(); + if (pDomainCache->GetConditionalAptcaDomainState() == ConditionalAptcaCache::kAllEnabled) + { + return true; + } + + CQuickBytes qbName; + LPWSTR pszName; + DWORD cbName = 0; + HRESULT hr = pAsmName->GetProperty(ASM_NAME_NAME, NULL, &cbName); + if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) + { + pszName = (LPWSTR)qbName.AllocThrows(cbName); + } + else + { + pDomainAssembly->ExternalLog(LL_ERROR, W("Rejecting native image / code sharing because there was an ") + W("error checking for conditional APTCA: 0x%x"), hr); + return false; + } + hr = pAsmName->GetProperty(ASM_NAME_NAME, (void *)pszName, &cbName); + if (FAILED(hr)) + { + pDomainAssembly->ExternalLog(LL_ERROR, W("Rejecting native image / code sharing because there was an ") + W("error checking for conditional APTCA: 0x%x"), hr); + return false; + } + BYTE rgPublicKeyToken[8]; + DWORD cbPkt = _countof(rgPublicKeyToken); + hr = pAsmName->GetProperty(ASM_NAME_PUBLIC_KEY_TOKEN, + (void*)rgPublicKeyToken, &cbPkt); + if (FAILED(hr)) + { + pDomainAssembly->ExternalLog(LL_ERROR, W("Rejecting native image / code sharing because there was an ") + W("error obtaining the public key token for %s: 0x%x"), + pszName, hr); + return false; + } + + GCX_COOP(); + + CLR_BOOL isVisible = FALSE; + + struct + { + OBJECTREF orThis; + } + gc; + ZeroMemory(&gc, sizeof(gc)); + GCPROTECT_BEGIN(gc); + gc.orThis = pDomainAssembly->GetAppDomain()->GetExposedObject(); + + MethodDescCallSite assemblyVisible(METHOD__APP_DOMAIN__IS_ASSEMBLY_ON_APTCA_VISIBLE_LIST_RAW, + &gc.orThis); + ARG_SLOT args[] = { + ObjToArgSlot(gc.orThis), + (ARG_SLOT)pszName, + (ARG_SLOT)wcslen(pszName), + (ARG_SLOT)rgPublicKeyToken, + (ARG_SLOT)cbPkt + }; + isVisible = assemblyVisible.Call_RetBool(args); + GCPROTECT_END(); + + return isVisible; + } + + bool IsAssemblyOnAptcaVisibleList(DomainAssembly *pAssembly) + { + CONTRACTL + { + STANDARD_VM_CHECK; + PRECONDITION(CheckPointer(pAssembly)); + PRECONDITION(GetAppDomain() == pAssembly->GetAppDomain()); + } + CONTRACTL_END; + + ConditionalAptcaCache *pDomainCache = pAssembly->GetAppDomain()->GetSecurityDescriptor()->GetConditionalAptcaCache(); + if (pDomainCache->GetConditionalAptcaDomainState() == ConditionalAptcaCache::kAllEnabled) + { + return true; + } + + GCX_COOP(); + + bool foundInList = false; + + // Otherwise, we need to transition into the BCL code to find out if the assembly is on the list + struct + { + OBJECTREF orAppDomain; + OBJECTREF orAssembly; + } + gc; + ZeroMemory(&gc, sizeof(gc)); + + GCPROTECT_BEGIN(gc); + + MethodDescCallSite isAssemblyOnAptcaVisibleList(METHOD__APP_DOMAIN__IS_ASSEMBLY_ON_APTCA_VISIBLE_LIST); + gc.orAppDomain = GetAppDomain()->GetExposedObject(); + gc.orAssembly = pAssembly->GetAssembly()->GetExposedObject(); + + ARG_SLOT args[] = + { + ObjToArgSlot(gc.orAppDomain), + ObjToArgSlot(gc.orAssembly) + }; + + foundInList = isAssemblyOnAptcaVisibleList.Call_RetBool(args); + + GCPROTECT_END(); + + return foundInList; + } + + //-------------------------------------------------------------------------------------------------------- + // + // Determine if an assembly is APTCA in the current domain or not + // + // Arguments: + // pDomainAssembly - Assembly to check for APTCA-ness + // tokenFlags - raw metadata security bits from the assembly + // + // Return Value: + // true if the assembly is APTCA, false if it is not + // + + bool IsAssemblyAptcaEnabled(DomainAssembly *pDomainAssembly, TokenSecurityDescriptorFlags tokenFlags) + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(CheckPointer(pDomainAssembly)); + } + CONTRACTL_END; + +#ifdef _DEBUG + SString strAptcaAssemblyBreak(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_Security_AptcaAssemblyBreak)); + SString strAssemblySimpleName(SString::Utf8, pDomainAssembly->GetSimpleName()); + if (strAptcaAssemblyBreak.EqualsCaseInsensitive(strAssemblySimpleName)) + { + _ASSERTE(!"Checking APTCA-ness of an APTCA break assembly"); + } +#endif // _DEBUG + + // If the assembly is not marked APTCA, then it cannot possibly be APTCA enabled + if ((tokenFlags & TokenSecurityDescriptorFlags_APTCA) == TokenSecurityDescriptorFlags_None) + { + return false; + } + + GCX_PREEMP(); + + // Additionally, if the assembly is on the APTCA kill list, then no matter what it says in its metadata, + // it should not be considered APTCA + if (GetKillBitList()->IsAssemblyKillBitted(pDomainAssembly->GetFile())) + { + return false; + } + + // If the assembly is conditionally APTCA, then we need to check the current AppDomain's APTCA enabled + // list to figure out if it is APTCA in this domain. + if (tokenFlags & TokenSecurityDescriptorFlags_ConditionalAPTCA) + { + return IsAssemblyOnAptcaVisibleList(pDomainAssembly); + } + + // Otherwise, the assembly is APTCA + return true; + } + + //-------------------------------------------------------------------------------------------------------- + // + // Determine if the assembly matches the conditional APTCA sharing mode. That is, if we are sharing + // enabled conditional APTCA assemblies check that this assembly is enabled. Similarly, if we are + // sharing disabled conditional APTCA assemblies check that this assembly is disabled. + // + // This method assumes that the assembly is conditionally APTCA + // + + bool AssemblyMatchesShareMode(IAssemblyName *pAsmName, DomainAssembly *pDomainAssembly) + { + CONTRACTL + { + STANDARD_VM_CHECK; + PRECONDITION(CheckPointer(pAsmName)); + PRECONDITION(GetConditionalAptcaSharingMode() != kShareUnknown); + } + CONTRACTL_END; + + if (IsAssemblyOnAptcaVisibleList(pAsmName, pDomainAssembly)) + { + return GetConditionalAptcaSharingMode() == kShareIfEnabled; + } + else + { + return GetConditionalAptcaSharingMode() == kShareIfDisabled; + } + } + + bool AssemblyMatchesShareMode(ConditionalAptcaCache::State state) + { + STANDARD_VM_CONTRACT; + + _ASSERTE(state == ConditionalAptcaCache::kEnabled || state == ConditionalAptcaCache::kDisabled); + + if (state == ConditionalAptcaCache::kEnabled) + { + return GetConditionalAptcaSharingMode() == kShareIfEnabled; + } + else + { + return GetConditionalAptcaSharingMode() == kShareIfDisabled; + } + } +} + +//------------------------------------------------------------------------------------------------------------ +// +// Determine if the AppDomain can share an assembly or if APTCA restrictions prevent sharing +// + +bool DomainCanShareAptcaAssembly(DomainAssembly *pDomainAssembly) +{ + CONTRACTL + { + STANDARD_VM_CHECK; + PRECONDITION(CheckPointer(pDomainAssembly)); + } + CONTRACTL_END; + +#ifdef _DEBUG + DWORD dwAptcaAssemblyDomainBreak = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_Security_AptcaAssemblySharingDomainBreak); + if (dwAptcaAssemblyDomainBreak == 0 || ADID(dwAptcaAssemblyDomainBreak) == pDomainAssembly->GetAppDomain()->GetId()) + { + SString strAptcaAssemblySharingBreak(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_Security_AptcaAssemblySharingBreak)); + SString strAssemblySimpleName(SString::Utf8, pDomainAssembly->GetSimpleName()); + + if (strAptcaAssemblySharingBreak.EqualsCaseInsensitive(strAssemblySimpleName)) + { + _ASSERTE(!"Checking code sharing for APTCA break assembly"); + } + } +#endif // _DEBUG + + // + // We can only share an assembly if all conditional APTCA assemblies in its full closure of dependencies + // are enabled. + // + + // We always allow sharing of mscorlib + if (pDomainAssembly->IsSystem()) + { + return true; + } + + IApplicationSecurityDescriptor *pDomainSecDesc = pDomainAssembly->GetAppDomain()->GetSecurityDescriptor(); + ConditionalAptcaCache *pConditionalAptcaCache = pDomainSecDesc->GetConditionalAptcaCache(); + + // If all assemblies in the domain match the sharing mode, then we can share the assembly + ConditionalAptcaCache::DomainState domainState = pConditionalAptcaCache->GetConditionalAptcaDomainState(); + if (GetConditionalAptcaSharingMode() == kShareIfEnabled) + { + if (domainState == ConditionalAptcaCache::kAllEnabled) + { + return true; + } + } + else + { + if (domainState == ConditionalAptcaCache::kAllDisabled) + { + return true; + } + } + + // If the root assembly is conditionally APTCA, then it needs to be enabled + ReleaseHolder pRootImport(pDomainAssembly->GetFile()->GetMDImportWithRef()); + TokenSecurityDescriptorFlags rootSecurityAttributes = + TokenSecurityDescriptor::ReadSecurityAttributes(pRootImport, TokenFromRid(1, mdtAssembly)); + if (rootSecurityAttributes & TokenSecurityDescriptorFlags_ConditionalAPTCA) + { + if (!AssemblyMatchesShareMode(pDomainAssembly->GetFile()->GetFusionAssemblyName(), pDomainAssembly)) + { + return false; + } + } + + // Now we need to get the full closure of assemblies that this assembly depends upon and ensure that each + // one of those is either not conditional APTCA or is enabled in the domain. We get a new assembly + // closure object here rather than using DomainAssembly::GetAssemblyBindingClosure because we don't want + // to force that closure to walk the full dependency graph (and therefore not be considered equal to + // closures which weren't fully walked). + IUnknown *pFusionAssembly; + if (pDomainAssembly->GetFile()->IsIStream()) + { + pFusionAssembly = pDomainAssembly->GetFile()->GetIHostAssembly(); + } + else + { + pFusionAssembly = pDomainAssembly->GetFile()->GetFusionAssembly(); + } + + // Get the closure and force it to do a full dependency walk, not stopping at framework assemblies + SafeComHolder pClosure; + + + LPCWSTR pNIPath = NULL; + PEAssembly *pPEAsm = pDomainAssembly->GetFile(); + if (pPEAsm->HasNativeImage()) + { + ReleaseHolder pNIImage = pPEAsm->GetNativeImageWithRef(); + pNIPath = pNIImage->GetPath().GetUnicode(); + } + + IfFailThrow(pDomainAssembly->GetAppDomain()->GetFusionContext()->GetAssemblyBindingClosure(pFusionAssembly, pNIPath, &pClosure)); + IfFailThrow(pClosure->EnsureWalked(pFusionAssembly, pDomainAssembly->GetAppDomain()->GetFusionContext(), LEVEL_FXPROBED)); + + // Now iterate the closure looking for conditional APTCA assemblies + SafeComHolder pClosureEnumerator; + IfFailThrow(pClosure->EnumerateAssemblies(&pClosureEnumerator)); + LPCOLESTR szDependentAssemblyPath = NULL; + LPCOLESTR szDependentNIAssemblyPath = NULL; + + for (HRESULT hr = pClosureEnumerator->GetNextAssemblyPath(&szDependentAssemblyPath, &szDependentNIAssemblyPath); + SUCCEEDED(hr); + hr = pClosureEnumerator->GetNextAssemblyPath(&szDependentAssemblyPath, &szDependentNIAssemblyPath)) + { + // Make sure we've succesfully enumerated an item + if (hr != S_OK && hr != HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS)) + { + pDomainAssembly->ExternalLog(LL_ERROR, W("Rejecting code sharing because of an error enumerating dependent assemblies: 0x%x"), hr); + return false; + } + else if (szDependentAssemblyPath == NULL) + { + // This means we have an assembly but no way to verify the image at this point -- should we get + // into this state, we'll be conservative and fail the share + pDomainAssembly->ExternalLog(LL_ERROR, W("Rejecting code sharing because an assembly in the closure does not have a path")); + return false; + } + else + { + // We have succesfully found a new item in the closure of assemblies - now check to ensure that + // it is either not conditionally APTCA or is enabled in tihs domain. + PEImageHolder pDependentImage; + + // Use the native image if it is loaded. + if (szDependentNIAssemblyPath != NULL) + { + SString strNIAssemblyPath(szDependentNIAssemblyPath); + pDependentImage = PEImage::OpenImage(strNIAssemblyPath, MDInternalImport_OnlyLookInCache); + if (pDependentImage != NULL && !pDependentImage->HasLoadedLayout()) + { + pDependentImage = NULL; + } + else + { +#if FEATURE_CORECLR +#error Coreclr needs to check native image version here. +#endif + } + } + + if (pDependentImage == NULL) + { + SString strAssemblyPath(szDependentAssemblyPath); + pDependentImage = PEImage::OpenImage(strAssemblyPath); + } + + // See if we already know if this image is enabled in the current domain or not + ConditionalAptcaCache::State dependentState = pConditionalAptcaCache->GetCachedState(pDependentImage); + + // We don't know this assembly's conditional APTCA state in this domain, so we need to figure it + // out now. + if (dependentState == ConditionalAptcaCache::kUnknown) + { + // First figure out if the assembly is even conditionally APTCA to begin with + IMDInternalImport *pDependentImport = pDependentImage->GetMDImport(); + TokenSecurityDescriptorFlags dependentSecurityAttributes = + TokenSecurityDescriptor::ReadSecurityAttributes(pDependentImport, TokenFromRid(1, mdtAssembly)); + + if (dependentSecurityAttributes & TokenSecurityDescriptorFlags_ConditionalAPTCA) + { + // The the assembly name of the dependent assembly so we can check it to the domain + // enabled list + ReleaseHolder pDependentAssemblyName; + AssemblySpec dependentAssemblySpec(pDomainAssembly->GetAppDomain()); + dependentAssemblySpec.InitializeSpec(TokenFromRid(1, mdtAssembly), pDependentImport); + IfFailThrow(dependentAssemblySpec.CreateFusionName(&pDependentAssemblyName, FALSE)); + + // Check the domain list to see if the assembly is on it + if (IsAssemblyOnAptcaVisibleList(pDependentAssemblyName, pDomainAssembly)) + { + dependentState = ConditionalAptcaCache::kEnabled; + } + else + { + dependentState = ConditionalAptcaCache::kDisabled; + } + } + else + { + // The dependent assembly doesn't have the conditional APTCA bit set on it, so we don't + // need to do any checking to see if it's enabled + dependentState = ConditionalAptcaCache::kNotCAptca; + } + + // Cache the result of evaluating conditional APTCA on this assembly in the domain + pConditionalAptcaCache->SetCachedState(pDependentImage, dependentState); + } + + // If the dependent assembly does not match the sharing mode, then we cannot share the + // dependency. We can always share dependencies which are not conditionally APTCA, so don't + // bother checking the share mode for them. + if (dependentState != ConditionalAptcaCache::kNotCAptca) + { + if (!AssemblyMatchesShareMode(dependentState)) + { + pDomainAssembly->ExternalLog(LL_ERROR, W("Rejecting code sharing because a dependent assembly did not match the conditional APTCA share mode")); + return false; + } + } + } + } + + // The root assembly and all of its dependents were either on the conditional APTCA list or are not + // conditional APTCA, so we can share this assembly + return true; +} + +//------------------------------------------------------------------------------------------------------------ +// +// Get an exception string indicating how to enable a conditional APTCA assembly if it was disabled and +// caused an exception +// + +SString GetConditionalAptcaAccessExceptionContext(Assembly *pTargetAssembly) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(CheckPointer(pTargetAssembly)); + } + CONTRACTL_END; + + SString exceptionContext; + + ModuleSecurityDescriptor *pMSD = ModuleSecurityDescriptor::GetModuleSecurityDescriptor(pTargetAssembly); + + if (pMSD->GetTokenFlags() & TokenSecurityDescriptorFlags_ConditionalAPTCA) + { + GCX_PREEMP(); + + if (!IsAssemblyOnAptcaVisibleList(pTargetAssembly->GetDomainAssembly())) + { + // We have a conditional APTCA assembly which is not on the visible list for the current + // AppDomain, provide information on how to enable it. + SString assemblyDisplayName; + pTargetAssembly->GetDisplayName(assemblyDisplayName); + + SString assemblyConditionalAptcaName; + GetAssemblyNameForConditionalAptca(pTargetAssembly, &assemblyConditionalAptcaName); + + EEException::GetResourceMessage(IDS_ACCESS_EXCEPTION_CONTEXT_CONDITIONAL_APTCA, + exceptionContext, + assemblyDisplayName, + assemblyConditionalAptcaName); + } + } + + return exceptionContext; +} + +//------------------------------------------------------------------------------------------------------------ +// +// Get an exception string indicating that an assembly was on the kill bit list if it caused an exception +// + +SString GetAptcaKillBitAccessExceptionContext(Assembly *pTargetAssembly) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(CheckPointer(pTargetAssembly)); + } + CONTRACTL_END; + + GCX_PREEMP(); + + SString exceptionContext; + + if (GetKillBitList()->IsAssemblyKillBitted(pTargetAssembly->GetDomainAssembly()->GetFile())) + { + SString assemblyDisplayName; + pTargetAssembly->GetDisplayName(assemblyDisplayName); + + EEException::GetResourceMessage(IDS_ACCESS_EXCEPTION_CONTEXT_APTCA_KILLBIT, + exceptionContext, + assemblyDisplayName); + } + + return exceptionContext; +} + +//------------------------------------------------------------------------------------------------------------ +// +// Determine if a native image is valid to use from the perspective of APTCA. This means that the image +// itself and all of its dependencies must: +// 1. Not be killbitted +// 2. Be enabled if they are conditionally APTCA +// +// Arguments: +// pNativeImage - native image to accept or reject +// pDomainAssembly - assembly that is being loaded +// +// Return Value: +// true if the native image can be accepted due to APTCA-ness, false if we need to reject it +// + +bool NativeImageHasValidAptcaDependencies(PEImage *pNativeImage, DomainAssembly *pDomainAssembly) +{ + CONTRACTL + { + STANDARD_VM_CHECK; + PRECONDITION(CheckPointer(pNativeImage)); + PRECONDITION(CheckPointer(pDomainAssembly)); + } + CONTRACTL_END; + + AptcaKillBitList *pKillBitList = GetKillBitList(); + + ConditionalAptcaCache *pDomainCache = pDomainAssembly->GetAppDomain()->GetSecurityDescriptor()->GetConditionalAptcaCache(); + // If we have any killbitted assemblies, then we need to make sure that the current assembly and its dependencies + BOOL aptcaChecks = pKillBitList->AreAnyAssembliesKillBitted(); + BOOL conditionalAptcaChecks = pDomainCache->GetConditionalAptcaDomainState() != ConditionalAptcaCache::kAllEnabled; + if (!aptcaChecks && !conditionalAptcaChecks) + return true; + + // + // Check to see if the NGEN image itself is APTCA and killbitted + // + + ReleaseHolder pAssemblyMD(pDomainAssembly->GetFile()->GetMDImportWithRef()); + TokenSecurityDescriptorFlags assemblySecurityAttributes = + TokenSecurityDescriptor::ReadSecurityAttributes(pAssemblyMD, TokenFromRid(1, mdtAssembly)); + + if (aptcaChecks) + { + if ((assemblySecurityAttributes & TokenSecurityDescriptorFlags_APTCA) && + pKillBitList->IsAssemblyKillBitted(pDomainAssembly->GetFile())) + { + return false; + } + } + if (conditionalAptcaChecks + && (assemblySecurityAttributes & TokenSecurityDescriptorFlags_ConditionalAPTCA)) + { + // + // First check to see if we're disabled. + // + + AssemblySpec spec; + spec.InitializeSpec(pDomainAssembly->GetFile()); + ReleaseHolder pAsmName; + IfFailThrow(spec.CreateFusionName(&pAsmName, FALSE)); + + if (!IsAssemblyOnAptcaVisibleList(pAsmName, pDomainAssembly)) + { + //IsAssemblyOnAptcaVisibleList has already logged an error. + return false; + } + } + + if (aptcaChecks || conditionalAptcaChecks) + { + // + // Also check its dependencies + // + + COUNT_T dependencyCount; + PEImageLayout *pNativeLayout = pNativeImage->GetLoadedLayout(); + CORCOMPILE_DEPENDENCY *pDependencies = pNativeLayout->GetNativeDependencies(&dependencyCount); + + for (COUNT_T i = 0; i < dependencyCount; ++i) + { + CORCOMPILE_DEPENDENCY* pDependency = &(pDependencies[i]); + // Look for any dependency which is APTCA + if (pDependencies[i].dwAssemblyDef != mdAssemblyRefNil) + { + AssemblySpec name; + name.InitializeSpec(pDependency->dwAssemblyRef, + pNativeImage->GetNativeMDImport(), + NULL, + pDomainAssembly->GetFile()->IsIntrospectionOnly()); + + ReleaseHolder pDependencyAssemblyName; + HRESULT hr = name.CreateFusionName(&pDependencyAssemblyName, FALSE); + + // If we couldn't build the assemlby name up conservatively discard the image + if (FAILED(hr)) + { + pDomainAssembly->ExternalLog(LL_ERROR, W("Rejecting native image because could not get ") + W("name for assemblyref 0x%x for native image dependency: ") + W("hr=0x%x"), pDependency->dwAssemblyRef, hr); + return false; + } + + if (pDependencies[i].dependencyInfo & (CORCOMPILE_DEPENDENCY_IS_APTCA)) + { + ULARGE_INTEGER fileVersion; + + //This is a workaround for Dev10# 743602 + fileVersion.QuadPart = GET_UNALIGNED_VAL64(&(pDependencies[i].uliFileVersion)); + // If the dependency really is killbitted, then discard the image + if (pKillBitList->IsAssemblyKillBitted(pDependencyAssemblyName, fileVersion)) + { + pDomainAssembly->ExternalLog(LL_ERROR, W("Rejecting native image because dependency ") + W("assemblyref 0x%x is killbitted."), + pDependency->dwAssemblyRef); + return false; + } + } + if (pDependencies[i].dependencyInfo & (CORCOMPILE_DEPENDENCY_IS_CAPTCA)) + { + if (!IsAssemblyOnAptcaVisibleList(pDependencyAssemblyName, pDomainAssembly)) + { + //IsAssemblyOnAptcaVisibleList has already logged an error. + return false; + } + } + } + } + } + return true; +} +#else // CROSSGEN_COMPILE +namespace +{ + bool IsAssemblyAptcaEnabled(DomainAssembly *pDomainAssembly, TokenSecurityDescriptorFlags tokenFlags) + { + // No killbits or conditional APTCA for crossgen. Just check whether the assembly is marked APTCA. + return ((tokenFlags & TokenSecurityDescriptorFlags_APTCA) != TokenSecurityDescriptorFlags_None); + } +} +#endif // CROSSGEN_COMPILE + +//------------------------------------------------------------------------------------------------------------ +// +// Process an assembly's real APTCA flags to determine if the assembly should be considered +// APTCA or not +// +// Arguments: +// pDomainAssembly - Assembly to check for APTCA-ness +// tokenFlags - raw metadata security bits from the assembly +// +// Return Value: +// updated token security descriptor flags which indicate the assembly's true APTCA state +// + +TokenSecurityDescriptorFlags ProcessAssemblyAptcaFlags(DomainAssembly *pDomainAssembly, + TokenSecurityDescriptorFlags tokenFlags) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(CheckPointer(pDomainAssembly)); + } + CONTRACTL_END; + + const TokenSecurityDescriptorFlags aptcaFlags = TokenSecurityDescriptorFlags_APTCA | + TokenSecurityDescriptorFlags_ConditionalAPTCA; + + if (IsAssemblyAptcaEnabled(pDomainAssembly, tokenFlags)) + { + // The assembly is APTCA - temporarially remove all of its APTCA bits, and then add back the + // unconditionally APTCA bit + tokenFlags = tokenFlags & ~aptcaFlags; + return tokenFlags | TokenSecurityDescriptorFlags_APTCA; + } + else + { + // The assembly is not APTCA, so remove all of its APTCA bits from the token security descriptor + return tokenFlags & ~aptcaFlags; + } +} diff --git a/src/vm/aptca.h b/src/vm/aptca.h new file mode 100644 index 0000000000..3d590a093a --- /dev/null +++ b/src/vm/aptca.h @@ -0,0 +1,110 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +//-------------------------------------------------------------------------- +// aptca.h +// +// Functions for handling allow partially trusted callers assemblies +// +// This should be the only interface for talking about the APTCA-ness of an assembly, and even then should +// be used only from very select areas of the CLR that absolutely need to know the information. For +// instance: +// +// * the class loader (for code sharing and formatting exception messages) +// * NGEN (for determining if a native image is valid) +// * security attribute processing code (for obvious reasons) +// +// may use this interface. Nearly every other section of the code should simply be relying on the +// ModuleSecurityDescriptor for the assembly in question. And no other sections of the code should be +// directly asking questions like "is this assembly conditional APTCA" ... we explicitly want to hide that +// information away behind the final assembly security attribute computation as much as possible. +// +// In particular, no code should be making security enforcement decisions based upon conditional APTCA, and +// instead should rely on the existing transparency / legacy APTCA enforcement. This means that once the +// security system, JIT, and class loader have finished setting up an assembly's APTCA attributes, there +// should be no further questions asked about the particular APTCA attribute applied to the assembly. +// +// Put another way, once an assembly is loaded, the APTCA kill bit and conditional APTCA enabled / disabled +// decision for an assembly should evaporate away, and all assemblies should look as if they either have a +// full APTCA attribute (in the not-killbitted / conditional APTCA enabled case) or no APTCA attribute at +// all (killbitted or conditional APTCA disabled). +// + +// +//-------------------------------------------------------------------------- + + +#ifndef __APTCA_H__ +#define __APTCA_H__ + +#ifndef FEATURE_APTCA +#error FEATURE_APTCA is required for this file +#endif // FEATURE_APTCA + +#include "securitymeta.h" + +class ConditionalAptcaCache +{ +public: + typedef enum + { + kUnknown, // No cached state + kEnabled, // The assembly is enabled in this domain + kDisabled, // The assembly is disabled in this domain + kNotCAptca, // The assembly is not conditionally APTCA + } + State; + + typedef enum + { + kDomainStateUnknown, // The domain state is not yet initialized + kAllEnabled, // All assemblies in the domain are enabled + kSomeEnabled, // Some assemblies in the domain are enabled + kAllDisabled, // All assemblies in the domain are disabled + } + DomainState; + + ConditionalAptcaCache(AppDomain *pAppDomain); + ~ConditionalAptcaCache(); + + State GetCachedState(PTR_PEImage pImage); + void SetCachedState(PTR_PEImage pImage, State state); + + DomainState GetConditionalAptcaDomainState(); + void SetCanonicalConditionalAptcaList(LPCWSTR wszCanonicalConditionalAptcaList); + + static bool ConsiderFullTrustConditionalAptcaLists(); + +private: + ConditionalAptcaCache(ConditionalAptcaCache &other); // not implemented - used to prevent compiler generating a copy constructor + ConditionalAptcaCache& operator=(const ConditionalAptcaCache &other); // not implemented - used to prevent compiler generating an assignment operator + +private: + AppDomain *m_pAppDomain; + + bool m_canonicalListIsNull; + SString m_canonicalList; + DomainState m_domainState; +}; + +// Determine if the AppDomain can share an assembly or if APTCA restrictions prevent sharing +bool DomainCanShareAptcaAssembly(DomainAssembly *pDomainAssembly); + +// Get an exception string indicating how to enable a conditional APTCA assembly if it was disabled and +// caused an exception +SString GetConditionalAptcaAccessExceptionContext(Assembly *pTargetAssembly); + +// Get an exception string indicating that +SString GetConditionalAptcaSharingExceptionContext(Assembly *pTargetAssembly); + +// Get an exception string indicating that an assembly was on the kill bit list if it caused an exception +SString GetAptcaKillBitAccessExceptionContext(Assembly *pTargetAssembly); + +// Determine if a native image is OK to use from an APTCA perspective (it and its dependencies all have the +// same APTCA-ness now as at NGEN time) +bool NativeImageHasValidAptcaDependencies(PEImage *pNativeImage, DomainAssembly *pDomainAssembly); + +// Process an assembly's real APTCA flags to determine if the assembly should be considered APTCA or not +TokenSecurityDescriptorFlags ProcessAssemblyAptcaFlags(DomainAssembly *pDomainAssembly, TokenSecurityDescriptorFlags tokenFlags); + +#endif // __APTCA_H__ diff --git a/src/vm/argdestination.h b/src/vm/argdestination.h new file mode 100644 index 0000000000..d8f6c854b2 --- /dev/null +++ b/src/vm/argdestination.h @@ -0,0 +1,218 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +// + +#ifndef __ARGDESTINATION_H__ +#define __ARGDESTINATION_H__ + +// The ArgDestination class represents a destination location of an argument. +class ArgDestination +{ + // Base address to which the m_offset is applied to get the actual argument location. + PTR_VOID m_base; + // Offset of the argument relative to the m_base. On AMD64 on Unix, it can have a special + // value that represent a struct that contain both general purpose and floating point fields + // passed in registers. + int m_offset; + // For structs passed in registers, this member points to an ArgLocDesc that contains + // details on the layout of the struct in general purpose and floating point registers. + ArgLocDesc* m_argLocDescForStructInRegs; + +public: + + // Construct the ArgDestination + ArgDestination(PTR_VOID base, int offset, ArgLocDesc* argLocDescForStructInRegs) + : m_base(base), + m_offset(offset), + m_argLocDescForStructInRegs(argLocDescForStructInRegs) + { + LIMITED_METHOD_CONTRACT; +#if defined(UNIX_AMD64_ABI) && defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) + _ASSERTE((argLocDescForStructInRegs != NULL) || (offset != TransitionBlock::StructInRegsOffset)); +#else + _ASSERTE(argLocDescForStructInRegs == NULL); +#endif + } + + // Get argument destination address for arguments that are not structs passed in registers. + PTR_VOID GetDestinationAddress() + { + LIMITED_METHOD_CONTRACT; + return dac_cast(dac_cast(m_base) + m_offset); + } + +#if defined(UNIX_AMD64_ABI) && defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) + + // Returns true if the ArgDestination represents a struct passed in registers. + bool IsStructPassedInRegs() + { + LIMITED_METHOD_CONTRACT; + return m_offset == TransitionBlock::StructInRegsOffset; + } + + // Get destination address for floating point fields of a struct passed in registers. + PTR_VOID GetStructFloatRegDestinationAddress() + { + LIMITED_METHOD_CONTRACT; + _ASSERTE(IsStructPassedInRegs()); + int offset = TransitionBlock::GetOffsetOfFloatArgumentRegisters() + m_argLocDescForStructInRegs->m_idxFloatReg * 16; + return dac_cast(dac_cast(m_base) + offset); + } + + // Get destination address for non-floating point fields of a struct passed in registers. + PTR_VOID GetStructGenRegDestinationAddress() + { + LIMITED_METHOD_CONTRACT; + _ASSERTE(IsStructPassedInRegs()); + int offset = TransitionBlock::GetOffsetOfArgumentRegisters() + m_argLocDescForStructInRegs->m_idxGenReg * 8; + return dac_cast(dac_cast(m_base) + offset); + } + +#ifndef DACCESS_COMPILE + // Zero struct argument stored in registers described by the current ArgDestination. + // Arguments: + // fieldBytes - size of the structure + void ZeroStructInRegisters(int fieldBytes) + { + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_MODE_COOPERATIVE; + + // To zero the struct, we create a zero filled array of large enough size and + // then copy it to the registers. It is implemented this way to keep the complexity + // of dealing with the eightbyte classification in single function. + // This function is used rarely and so the overhead of reading the zeros from + // the stack is negligible. + long long zeros[CLR_SYSTEMV_MAX_EIGHTBYTES_COUNT_TO_PASS_IN_REGISTERS] = {}; + _ASSERTE(sizeof(zeros) >= fieldBytes); + + CopyStructToRegisters(zeros, fieldBytes, 0); + } + + // Copy struct argument into registers described by the current ArgDestination. + // Arguments: + // src = source data of the structure + // fieldBytes - size of the structure + // destOffset - nonzero when copying values into Nullable, it is the offset + // of the T value inside of the Nullable + void CopyStructToRegisters(void *src, int fieldBytes, int destOffset) + { + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_MODE_COOPERATIVE; + + _ASSERTE(IsStructPassedInRegs()); + + BYTE* genRegDest = (BYTE*)GetStructGenRegDestinationAddress() + destOffset; + BYTE* floatRegDest = (BYTE*)GetStructFloatRegDestinationAddress(); + INDEBUG(int remainingBytes = fieldBytes;) + + EEClass* eeClass = m_argLocDescForStructInRegs->m_eeClass; + _ASSERTE(eeClass != NULL); + + // We start at the first eightByte that the destOffset didn't skip completely. + for (int i = destOffset / 8; i < eeClass->GetNumberEightBytes(); i++) + { + int eightByteSize = eeClass->GetEightByteSize(i); + SystemVClassificationType eightByteClassification = eeClass->GetEightByteClassification(i); + + // Adjust the size of the first eightByte by the destOffset + eightByteSize -= (destOffset & 7); + destOffset = 0; + + _ASSERTE(remainingBytes >= eightByteSize); + + if (eightByteClassification == SystemVClassificationTypeSSE) + { + if (eightByteSize == 8) + { + *(UINT64*)floatRegDest = *(UINT64*)src; + } + else + { + _ASSERTE(eightByteSize == 4); + *(UINT32*)floatRegDest = *(UINT32*)src; + } + floatRegDest += 16; + } + else + { + if (eightByteSize == 8) + { + _ASSERTE((eightByteClassification == SystemVClassificationTypeInteger) || + (eightByteClassification == SystemVClassificationTypeIntegerReference) || + (eightByteClassification == SystemVClassificationTypeIntegerByRef)); + + _ASSERTE(IS_ALIGNED((SIZE_T)genRegDest, 8)); + *(UINT64*)genRegDest = *(UINT64*)src; + } + else + { + _ASSERTE(eightByteClassification == SystemVClassificationTypeInteger); + memcpyNoGCRefs(genRegDest, src, eightByteSize); + } + + genRegDest += eightByteSize; + } + + src = (BYTE*)src + eightByteSize; + INDEBUG(remainingBytes -= eightByteSize;) + } + + _ASSERTE(remainingBytes == 0); + } + +#endif //DACCESS_COMPILE + + // Report managed object pointers in the struct in registers + // Arguments: + // fn - promotion function to apply to each managed object pointer + // sc - scan context to pass to the promotion function + // fieldBytes - size of the structure + void ReportPointersFromStructInRegisters(promote_func *fn, ScanContext *sc, int fieldBytes) + { + LIMITED_METHOD_CONTRACT; + + _ASSERTE(IsStructPassedInRegs()); + + TADDR genRegDest = dac_cast(GetStructGenRegDestinationAddress()); + INDEBUG(int remainingBytes = fieldBytes;) + + EEClass* eeClass = m_argLocDescForStructInRegs->m_eeClass; + _ASSERTE(eeClass != NULL); + + for (int i = 0; i < eeClass->GetNumberEightBytes(); i++) + { + int eightByteSize = eeClass->GetEightByteSize(i); + SystemVClassificationType eightByteClassification = eeClass->GetEightByteClassification(i); + + _ASSERTE(remainingBytes >= eightByteSize); + + if (eightByteClassification != SystemVClassificationTypeSSE) + { + if ((eightByteClassification == SystemVClassificationTypeIntegerReference) || + (eightByteClassification == SystemVClassificationTypeIntegerByRef)) + { + _ASSERTE(eightByteSize == 8); + _ASSERTE(IS_ALIGNED((SIZE_T)genRegDest, 8)); + + (*fn)(dac_cast(genRegDest), sc, 0); + } + + genRegDest += eightByteSize; + } + + INDEBUG(remainingBytes -= eightByteSize;) + } + + _ASSERTE(remainingBytes == 0); + } + +#endif // UNIX_AMD64_ABI && FEATURE_UNIX_AMD64_STRUCT_PASSING + +}; + +#endif // __ARGDESTINATION_H__ diff --git a/src/vm/argslot.h b/src/vm/argslot.h new file mode 100644 index 0000000000..9729227eed --- /dev/null +++ b/src/vm/argslot.h @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +// ============================================================================ +// File: argslot.h +// + +// ============================================================================ +// Contains the ARG_SLOT type. + + +#ifndef __ARG_SLOT_H__ +#define __ARG_SLOT_H__ + +// The ARG_SLOT must be big enough to represent all pointer and basic types (except for 80-bit fp values). +// So, it's guaranteed to be at least 64-bit. +typedef unsigned __int64 ARG_SLOT; +#define SIZEOF_ARG_SLOT 8 + +#if BIGENDIAN +// Returns the address of the payload inside the argslot +inline BYTE* ArgSlotEndianessFixup(ARG_SLOT* pArg, UINT cbSize) { + LIMITED_METHOD_CONTRACT; + + BYTE* pBuf = (BYTE*)pArg; + switch (cbSize) { + case 1: + pBuf += 7; + break; + case 2: + pBuf += 6; + break; + case 4: + pBuf += 4; + break; + } + return pBuf; +} +#else +#define ArgSlotEndianessFixup(pArg, cbSize) ((BYTE *)(pArg)) +#endif + +#endif // __ARG_SLOT_H__ diff --git a/src/vm/arm/.gitmirror b/src/vm/arm/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/vm/arm/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file diff --git a/src/vm/arm/CrtHelpers.asm b/src/vm/arm/CrtHelpers.asm new file mode 100644 index 0000000000..6ba04b5971 --- /dev/null +++ b/src/vm/arm/CrtHelpers.asm @@ -0,0 +1,162 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; ==++== +; + +; +; ==--== +; *********************************************************************** +; File: CrtHelpers.asm +; +; *********************************************************************** + +#include "ksarm.h" + +#include "asmconstants.h" + +#include "asmmacros.h" + + TEXTAREA + +; JIT_MemSet/JIT_MemCpy +; +; It is IMPORANT that the exception handling code is able to find these guys +; on the stack, but to keep them from being tailcalled by VC++ we need to turn +; off optimization and it ends up being a wasteful implementation. +; +; Hence these assembly helpers. +; +;EXTERN_C void __stdcall JIT_MemSet(void* _dest, int c, size_t count) + LEAF_ENTRY JIT_MemSet + +; +; The memset function sets the first count bytes of +; dest to the character c (r1). +; +; Doesn't return a value +; + + subs r2, r2, #4 + blt ByteSet + + ands r1, r1, #&FF + orr r1, r1, r1, lsl #8 +CheckAlign ; 2-3 cycles + ands r3, r0, #3 ; Check alignment and fix if possible + bne Align + +BlockSet ; 6-7 cycles + orr r1, r1, r1, lsl #16 + subs r2, r2, #12 + mov r3, r1 + blt BlkSet8 + +BlkSet16 ; 7 cycles/16 bytes + stm r0!, {r1, r3} + subs r2, r2, #16 + stm r0!, {r1, r3} + bge BlkSet16 + +BlkSet8 ; 4 cycles/8 bytes + adds r2, r2, #8 + blt BlkSet4 + stm r0!, {r1, r3} + sub r2, r2, #8 + +BlkSet4 + adds r2, r2, #4 ; 4 cycles/4 bytes + blt ByteSet + str r1, [r0], #4 + b MaybeExit + +ByteSet + adds r2, r2, #4 +MaybeExit + beq ExitMemSet + + strb r1, [r0] ; 5 cycles/1-3bytes + cmp r2, #2 + blt ExitMemSet + strb r1, [r0, #1] + strbgt r1, [r0, #2] + +ExitMemSet + + bx lr + +Align ; 8 cycles/1-3 bytes + tst r0, #1 ; Check byte alignment + beq AlignHalf + subs r2, r2, #1 + strb r1, [r0], #1 +AlignHalf + tst r0, #2 ; Check Half-word alignment + beq BlockSet + subs r2, r2, #2 + strh r1, [r0], #2 + b BlockSet + + LEAF_END_MARKED JIT_MemSet + + +;EXTERN_C void __stdcall JIT_MemCpy(void* _dest, const void *_src, size_t count) + LEAF_ENTRY JIT_MemCpy +; +; It only requires 4 byte alignment +; and doesn't return a value + + cmp r2, #0 ; quick check for 0 length + beq ExitMemCpy ; if zero, exit + + tst r0, #3 ; skip directly to aligned if already aligned + beq DestAligned ; if 0, we're already aligned; go large + +ByteLoop1 + subs r2, r2, #1 ; decrement byte counter + ldrb r3, [r1], #1 ; copy one byte + strb r3, [r0], #1 + beq ExitMemCpy ; if the byte counter hits 0, exit early + tst r0, #3 ; are we aligned now? + bne ByteLoop1 ; nope, keep going + +DestAligned + subs r2, r2, #8 ; byte counter -= 8 + blt AlignedFinished ; if that puts us negative, skip the big copy + + tst r1, #3 ; is the 4-byte source aligned? + addne r2, r2, #8 ; if not, fix the byte counter (+= 8) + bne ByteLoop2 ; and do all the rest with bytes + +QwordLoop + subs r2, r2, #8 ; decrement byte counter by 8 + ldm r1!, {r3,r12} ; copy one qword + stm r0!, {r3,r12} ; + bge QwordLoop ; loop until the byte counter goes negative + +AlignedFinished + adds r2, r2, #4 ; add 4 to recover a potential >= 4-byte tail + blt AlignedFinished2 + ldr r3, [r1], #4 + str r3, [r0], #4 + b MaybeExitMemCpy +AlignedFinished2 + adds r2, r2, #4 ; add 4 more to the byte counter to recover + +MaybeExitMemCpy + beq ExitMemCpy ; the remaining count + +ByteLoop2 + subs r2, r2, #1 ; decrement the counter + ldrb r3, [r1], #1 ; copy one byte + strb r3, [r0], #1 + bne ByteLoop2 ; loop until the counter hits 0 + +ExitMemCpy + bx lr + + LEAF_END_MARKED JIT_MemCpy + + END + diff --git a/src/vm/arm/PInvokeStubs.asm b/src/vm/arm/PInvokeStubs.asm new file mode 100644 index 0000000000..791d66ab80 --- /dev/null +++ b/src/vm/arm/PInvokeStubs.asm @@ -0,0 +1,142 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +;; ==++== +;; + +;; +;; ==--== +#include "ksarm.h" + +#include "asmconstants.h" + +#include "asmmacros.h" + + + IMPORT VarargPInvokeStubWorker + IMPORT GenericPInvokeCalliStubWorker + + +; ------------------------------------------------------------------ +; Macro to generate PInvoke Stubs. +; $__PInvokeStubFuncName : function which calls the actual stub obtained from VASigCookie +; $__PInvokeGenStubFuncName : function which generates the IL stubs for PInvoke +; +; Params :- +; $FuncPrefix : prefix of the function name for the stub +; Eg. VarargPinvoke, GenericPInvokeCalli +; $VASigCookieReg : register which contains the VASigCookie +; $SaveFPArgs : "Yes" or "No" . For varidic functions FP Args are not present in FP regs +; So need not save FP Args registers for vararg Pinvoke + MACRO + + PINVOKE_STUB $FuncPrefix,$VASigCookieReg,$SaveFPArgs + + GBLS __PInvokeStubFuncName + GBLS __PInvokeGenStubFuncName + GBLS __PInvokeStubWorkerName + + IF "$FuncPrefix" == "GenericPInvokeCalli" +__PInvokeStubFuncName SETS "$FuncPrefix":CC:"Helper" + ELSE +__PInvokeStubFuncName SETS "$FuncPrefix":CC:"Stub" + ENDIF +__PInvokeGenStubFuncName SETS "$FuncPrefix":CC:"GenILStub" +__PInvokeStubWorkerName SETS "$FuncPrefix":CC:"StubWorker" + + IF "$VASigCookieReg" == "r1" +__PInvokeStubFuncName SETS "$__PInvokeStubFuncName":CC:"_RetBuffArg" +__PInvokeGenStubFuncName SETS "$__PInvokeGenStubFuncName":CC:"_RetBuffArg" + ENDIF + + NESTED_ENTRY $__PInvokeStubFuncName + + ; save reg value before using the reg + PROLOG_PUSH {$VASigCookieReg} + + ; get the stub + ldr $VASigCookieReg, [$VASigCookieReg,#VASigCookie__pNDirectILStub] + + ; if null goto stub generation + cbz $VASigCookieReg, %0 + + EPILOG_STACK_FREE 4 + + EPILOG_BRANCH_REG $VASigCookieReg + +0 + + EPILOG_POP {$VASigCookieReg} + EPILOG_BRANCH $__PInvokeGenStubFuncName + + NESTED_END + + + NESTED_ENTRY $__PInvokeGenStubFuncName + + PROLOG_WITH_TRANSITION_BLOCK 0, $SaveFPArgs + + ; r2 = UnmanagedTarget\ MethodDesc + mov r2, r12 + + ; r1 = VaSigCookie + IF "$VASigCookieReg" != "r1" + mov r1, $VASigCookieReg + ENDIF + + ; r0 = pTransitionBlock + add r0, sp, #__PWTB_TransitionBlock + + ; save hidden arg + mov r4, r12 + + bl $__PInvokeStubWorkerName + + ; restore hidden arg (method desc or unmanaged target) + mov r12, r4 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + + EPILOG_BRANCH $__PInvokeStubFuncName + + NESTED_END + + MEND + + + TEXTAREA +; ------------------------------------------------------------------ +; VarargPInvokeStub & VarargPInvokeGenILStub +; There is a separate stub when the method has a hidden return buffer arg. +; +; in: +; r0 = VASigCookie* +; r12 = MethodDesc * +; + PINVOKE_STUB VarargPInvoke, r0, {false} + + +; ------------------------------------------------------------------ +; GenericPInvokeCalliHelper & GenericPInvokeCalliGenILStub +; Helper for generic pinvoke calli instruction +; +; in: +; r4 = VASigCookie* +; r12 = Unmanaged target +; + PINVOKE_STUB GenericPInvokeCalli, r4, {true} + +; ------------------------------------------------------------------ +; VarargPInvokeStub_RetBuffArg & VarargPInvokeGenILStub_RetBuffArg +; Vararg PInvoke Stub when the method has a hidden return buffer arg +; +; in: +; r1 = VASigCookie* +; r12 = MethodDesc* +; + PINVOKE_STUB VarargPInvoke, r1, {false} + + +; Must be at very end of file + END diff --git a/src/vm/arm/armsinglestepper.cpp b/src/vm/arm/armsinglestepper.cpp new file mode 100644 index 0000000000..e000959ef9 --- /dev/null +++ b/src/vm/arm/armsinglestepper.cpp @@ -0,0 +1,1212 @@ +// 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. +// + +// +// Emulate hardware single-step on ARM. +// + +#include "common.h" +#include "armsinglestepper.h" + +// +// ITState methods. +// + +ITState::ITState() +{ +#ifdef _DEBUG + m_fValid = false; +#endif +} + +// Must call Get() (or Init()) to initialize this instance from a specific context before calling any other +// (non-static) method. +void ITState::Get(T_CONTEXT *pCtx) +{ + m_bITState = (BYTE)((BitExtract((WORD)pCtx->Cpsr, 15, 10) << 2) | + BitExtract((WORD)(pCtx->Cpsr >> 16), 10, 9)); +#ifdef _DEBUG + m_fValid = true; +#endif +} + +// Must call Init() (or Get()) to initialize this instance from a raw byte value before calling any other +// (non-static) method. +void ITState::Init(BYTE bState) +{ + m_bITState = bState; +#ifdef _DEBUG + m_fValid = true; +#endif +} + +// Does the current IT state indicate we're executing within an IT block? +bool ITState::InITBlock() +{ + _ASSERTE(m_fValid); + return (m_bITState & 0x1f) != 0; +} + +// Only valid within an IT block. Returns the condition code which will be evaluated for the current +// instruction. +DWORD ITState::CurrentCondition() +{ + _ASSERTE(m_fValid); + _ASSERTE(InITBlock()); + return BitExtract(m_bITState, 7, 4); +} + +// Transition the IT state to that for the next instruction. +void ITState::Advance() +{ + _ASSERTE(m_fValid); + if ((m_bITState & 0x7) == 0) + m_bITState = 0; + else + m_bITState = (m_bITState & 0xe0) | ((m_bITState << 1) & 0x1f); +} + +// Write the current IT state back into the given context. +void ITState::Set(T_CONTEXT *pCtx) +{ + _ASSERTE(m_fValid); + + Clear(pCtx); + pCtx->Cpsr |= BitExtract(m_bITState, 1, 0) << 25; + pCtx->Cpsr |= BitExtract(m_bITState, 7, 2) << 10; +} + +// Clear IT state (i.e. force execution to be outside of an IT block) in the given context. +/* static */ void ITState::Clear(T_CONTEXT *pCtx) +{ + pCtx->Cpsr &= 0xf9ff03ff; +} + +// +// ArmSingleStepper methods. +// +ArmSingleStepper::ArmSingleStepper() + : m_originalPc(0), m_targetPc(0), m_rgCode(0), m_state(Disabled), + m_fEmulatedITInstruction(false), m_fRedirectedPc(false), m_fEmulate(false), m_fBypass(false), m_fSkipIT(false) +{ + m_opcodes[0] = 0; + m_opcodes[1] = 0; +} + +ArmSingleStepper::~ArmSingleStepper() +{ +#if !defined(DACCESS_COMPILE) && !defined(FEATURE_PAL) + DeleteExecutable(m_rgCode); +#endif +} + +void ArmSingleStepper::Init() +{ +#if !defined(DACCESS_COMPILE) && !defined(FEATURE_PAL) + if (m_rgCode == NULL) + { + m_rgCode = new (executable) WORD[kMaxCodeBuffer]; + } +#endif +} + +// Given the context with which a thread will be resumed, modify that context such that resuming the thread +// will execute a single instruction before raising an EXCEPTION_BREAKPOINT. The thread context must be +// cleaned up via the Fixup method below before any further exception processing can occur (at which point the +// caller can behave as though EXCEPTION_SINGLE_STEP was raised). +void ArmSingleStepper::Enable() +{ + _ASSERTE(m_state != Applied); + + if (m_state == Enabled) + { + // We allow single-stepping to be enabled multiple times before the thread is resumed, but we require + // that the thread state is the same in all cases (i.e. additional step requests are treated as + // no-ops). + _ASSERTE(!m_fBypass); + _ASSERTE(m_opcodes[0] == 0); + _ASSERTE(m_opcodes[1] == 0); + + return; + } + + LOG((LF_CORDB, LL_INFO100000, "ArmSingleStepper::Enable\n")); + + m_fBypass = false; + m_opcodes[0] = 0; + m_opcodes[1] = 0; + m_state = Enabled; +} + +void ArmSingleStepper::Bypass(DWORD ip, WORD opcode1, WORD opcode2) +{ + _ASSERTE(m_state != Applied); + + if (m_state == Enabled) + { + // We allow single-stepping to be enabled multiple times before the thread is resumed, but we require + // that the thread state is the same in all cases (i.e. additional step requests are treated as + // no-ops). + if (m_fBypass) + { + _ASSERTE(m_opcodes[0] == opcode1); + _ASSERTE(m_opcodes[1] == opcode2); + _ASSERTE(m_originalPc == ip); + return; + } + } + + + LOG((LF_CORDB, LL_INFO100000, "ArmSingleStepper::Bypass(pc=%x, opcode=%x %x)\n", (DWORD)ip, (DWORD)opcode1, (DWORD)opcode2)); + + m_fBypass = true; + m_originalPc = ip; + m_opcodes[0] = opcode1; + m_opcodes[1] = opcode2; + m_state = Enabled; +} + +void ArmSingleStepper::Apply(T_CONTEXT *pCtx) +{ + if (m_rgCode == NULL) + { + Init(); + + // OOM. We will simply ignore the single step. + if (m_rgCode == NULL) + return; + } + + _ASSERTE(pCtx != NULL); + + if (!m_fBypass) + { + DWORD pc = ((DWORD)pCtx->Pc) & ~THUMB_CODE; + m_opcodes[0] = *(WORD*)pc; + if (Is32BitInstruction( m_opcodes[0])) + m_opcodes[1] = *(WORD*)(pc+2); + } + + WORD opcode1 = m_opcodes[0]; + WORD opcode2 = m_opcodes[1]; + + LOG((LF_CORDB, LL_INFO100000, "ArmSingleStepper::Apply(pc=%x, opcode=%x %x)\n", + (DWORD)pCtx->Pc, (DWORD)opcode1, (DWORD)opcode2)); + +#ifdef _DEBUG + // Make sure that we aren't trying to step through our own buffer. If this asserts, something is horribly + // wrong with the debugging layer. Likely GetManagedStoppedCtx is retrieving a Pc that points to our + // buffer, even though the single stepper is disabled. + DWORD codestart = (DWORD)(DWORD_PTR)m_rgCode; + DWORD codeend = codestart + (kMaxCodeBuffer * sizeof(WORD)); + _ASSERTE((pCtx->Pc < codestart) || (pCtx->Pc >= codeend)); +#endif + + // All stepping is simulated using a breakpoint instruction. Since other threads are not suspended while + // we step, we avoid race conditions and other complexity by redirecting the thread into a thread-local + // execution buffer. We can either copy the instruction we wish to step over into the buffer followed by a + // breakpoint or we can emulate the instruction (which is useful for instruction that depend on the value + // of the PC or that branch or call to an alternate location). Even in the emulation case we still + // redirect execution into the buffer and insert a breakpoint; this simplifies our interface since the + // rest of the runtime is not set up to expect single stepping to occur inline. Instead there is always a + // 1:1 relationship between setting the single-step mode and receiving an exception once the thread is + // restarted. + // + // There are two parts to the emulation: + // 1) In this method we either emulate the instruction (updating the input thread context as a result) or + // copy the single instruction into the execution buffer. In both cases we copy a breakpoint into the + // execution buffer as well then update the thread context to redirect execution into this buffer. + // 2) In the runtime's first chance vectored exception handler we perform the necessary fixups to make + // the exception look like the result of a single step. This includes resetting the PC to its correct + // value (either the instruction following the stepped instruction or the target PC cached in this + // object when we emulated an instruction that alters the PC). It also involves switching + // EXCEPTION_BREAKPOINT to EXCEPTION_SINGLE_STEP. + // + // If we encounter an exception while emulating an instruction (currently this can only happen if we A/V + // trying to read a value from memory) then we abandon emulation and fall back to the copy instruction + // mechanism. When we run the execution buffer the exception should be raised and handled as normal (we + // still peform context fixup in this case but we don't attempt to alter any exception code other than + // EXCEPTION_BREAKPOINT to EXCEPTION_SINGLE_STEP). There is a very small timing window here where another + // thread could alter memory protections to avoid the A/V when we run the instruction for real but the + // liklihood of this happening (in managed code no less) is judged sufficiently small that it's not worth + // the alternate solution (where we'd have to set the thread up to raise an exception with exactly the + // right thread context). + // + // Matters are complicated by the ARM IT instruction (upto four following instructions are executed + // conditionally based on a single condition or its negation). The issues are that the current instruction + // may be rendered into a no-op or that a breakpoint immediately following the current instruction may not + // be executed. To simplify matters we may modify the IT state to force our instructions to execute. We + // cache the real state and re-apply it along with the rest of our fixups when handling the breakpoint + // exception. Note that when executing general instructions we can't simply disable any IT state since + // many instructions alter their behavior depending on whether they're executing within an IT block + // (mostly it's used to determine whether these instructions set condition flags or not). + + // Cache thread's initial PC and IT state since we'll overwrite them as part of the emulation and we need + // to get back to the correct values at fixup time. We also cache a target PC (set below) since some + // instructions will set the PC directly or otherwise make it difficult for us to compute the final PC + // from the original. We still need the original PC however since this is the one we'll use if an + // exception (other than a breakpoint) occurs. + _ASSERTE(!m_fBypass || (m_originalPc == pCtx->Pc)); + + m_originalPc = pCtx->Pc; + m_originalITState.Get(pCtx); + + // By default assume the next PC is right after the current instruction. + m_targetPc = m_originalPc + (Is32BitInstruction(opcode1) ? 4 : 2); + m_fEmulate = false; + + // One more special case: if we attempt to single-step over an IT instruction it's easier to emulate this, + // set the new IT state in m_originalITState and set a special flag that lets Fixup() know we don't need + // to advance the state (this only works because we know IT will never raise an exception so we don't need + // m_originalITState to store the real original IT state, though in truth a legal IT instruction cannot be + // executed inside an IT block anyway). This flag (and m_originalITState) will be set inside TryEmulate() + // as needed. + m_fEmulatedITInstruction = false; + m_fSkipIT = false; + + // There are three different scenarios we must deal with (listed in priority order). In all cases we will + // redirect the thread to execute code from our buffer and end by raising a breakpoint exception: + // 1) We're executing in an IT block and the current instruction doesn't meet the condition requirements. + // We leave the state unchanged and in fixup will advance the PC to the next instruction slot. + // 2) The current instruction either takes the PC as an input or modifies the PC in a non-trivial manner. + // We can't easily run these instructions from the redirect buffer so we emulate their effect (i.e. + // update the current context in the same way as executing the instruction would). The breakpoint + // fixup logic will restore the PC to the real resultant PC we cache in m_targetPc. + // 3) For all other cases (including emulation cases where we aborted due to a memory fault) we copy the + // single instruction into the redirect buffer for execution followed by a breakpoint (once we regain + // control in the breakpoint fixup logic we can then reset the PC to its proper location. + + DWORD idxNextInstruction = 0; + + if (m_originalITState.InITBlock() && !ConditionHolds(pCtx, m_originalITState.CurrentCondition())) + { + LOG((LF_CORDB, LL_INFO100000, "ArmSingleStepper: Case 1: ITState::Clear;\n")); + // Case 1: The current instruction is a no-op because due to the IT instruction. We've already set the + // target PC to the next instruction slot. Disable the IT block since we want our breakpoint + // to execute. We'll put the correct value back during fixup. + ITState::Clear(pCtx); + m_fSkipIT = true; + m_rgCode[idxNextInstruction++] = kBreakpointOp; + } + else if (TryEmulate(pCtx, opcode1, opcode2, false)) + { + LOG((LF_CORDB, LL_INFO100000, "ArmSingleStepper: Case 2: Emulate\n")); + // Case 2: Successfully emulated an instruction that reads or writes the PC. Cache the new target PC + // so upon fixup we'll resume execution there rather than the following instruction. No need + // to mess with IT state since we know the next instruction is scheduled to execute (we dealt + // with the case where it wasn't above) and we're going to execute a breakpoint in that slot. + m_targetPc = pCtx->Pc; + m_fEmulate = true; + + // Set breakpoints to stop the execution. This will get us right back here. + m_rgCode[idxNextInstruction++] = kBreakpointOp; + m_rgCode[idxNextInstruction++] = kBreakpointOp; + } + else + { + LOG((LF_CORDB, LL_INFO100000, "ArmSingleStepper: Case 3: CopyInstruction. Is32Bit=%d\n", (DWORD)Is32BitInstruction(opcode1))); + // Case 3: In all other cases copy the instruction to the buffer and we'll run it directly. If we're + // in an IT block there could be up to three instructions following this one whose execution + // is skipped. We could try to be clever here and either alter IT state to force the next + // instruction to execute or calculate the how many filler instructions we need to insert + // before we're guaranteed our breakpoint will be respected. But it's easier to just insert + // three additional breakpoints here (code below will add the fourth) and that way we'll + // guarantee one of them will be hit (we don't care which one -- the fixup code will update + // the PC and IT state to make it look as though the CPU just executed the current + // instruction). + m_rgCode[idxNextInstruction++] = opcode1; + if (Is32BitInstruction(opcode1)) + m_rgCode[idxNextInstruction++] = opcode2; + + m_rgCode[idxNextInstruction++] = kBreakpointOp; + m_rgCode[idxNextInstruction++] = kBreakpointOp; + m_rgCode[idxNextInstruction++] = kBreakpointOp; + } + + // Always terminate the redirection buffer with a breakpoint. + m_rgCode[idxNextInstruction++] = kBreakpointOp; + _ASSERTE(idxNextInstruction <= kMaxCodeBuffer); + + // Set the thread up so it will redirect to our buffer when execution resumes. + pCtx->Pc = ((DWORD)(DWORD_PTR)m_rgCode) | THUMB_CODE; + + // Make sure the CPU sees the updated contents of the buffer. + FlushInstructionCache(GetCurrentProcess(), m_rgCode, sizeof(m_rgCode)); + + // Done, set the state. + m_state = Applied; +} + +void ArmSingleStepper::Disable() +{ + _ASSERTE(m_state != Applied); + m_state = Disabled; +} + +// When called in response to an exception (preferably in a first chance vectored handler before anyone else +// has looked at the thread context) this method will (a) determine whether this exception was raised by a +// call to Enable() above, in which case true will be returned and (b) perform final fixup of the thread +// context passed in to complete the emulation of a hardware single step. Note that this routine must be +// called even if the exception code is not EXCEPTION_BREAKPOINT since the instruction stepped might have +// raised its own exception (e.g. A/V) and we still need to fix the thread context in this case. +bool ArmSingleStepper::Fixup(T_CONTEXT *pCtx, DWORD dwExceptionCode) +{ +#ifdef _DEBUG + DWORD codestart = (DWORD)(DWORD_PTR)m_rgCode; + DWORD codeend = codestart + (kMaxCodeBuffer * sizeof(WORD)); +#endif + + // If we reach fixup, we should either be Disabled or Applied. If we reach here with Enabled it means + // that the debugging layer Enabled the single stepper, but we never applied it to a CONTEXT. + _ASSERTE(m_state != Enabled); + + // Nothing to do if the stepper is disabled on this thread. + if (m_state == Disabled) + { + // We better not be inside our internal code buffer though. + _ASSERTE((pCtx->Pc < codestart) || (pCtx->Pc >= codeend)); + return false; + } + + // Turn off the single stepper after we have executed one instruction. + m_state = Disabled; + + // We should always have a PC somewhere in our redirect buffer. +#ifdef _DEBUG + _ASSERTE((pCtx->Pc >= codestart) && (pCtx->Pc < codeend)); +#endif + + if (dwExceptionCode == EXCEPTION_BREAKPOINT) + { + // The single step went as planned. Set the PC back to its real value (either following the + // instruction we stepped or the computed destination we cached after emulating an instruction that + // modifies the PC). Advance the IT state from the value we cached before the single step (unless we + // stepped an IT instruction itself, in which case m_originalITState holds the new state and we should + // just set that). + if (!m_fEmulate) + { + if (m_rgCode[0] != kBreakpointOp) + { + LOG((LF_CORDB, LL_INFO100000, "ArmSingleStepper::Fixup executed code, ip = %x\n", m_targetPc)); + + pCtx->Pc = m_targetPc; + if (!m_fEmulatedITInstruction) + m_originalITState.Advance(); + + m_originalITState.Set(pCtx); + } + else + { + if (m_fSkipIT) + { + // We needed to skip over an instruction due to a false condition in an IT block. + LOG((LF_CORDB, LL_INFO100000, "ArmSingleStepper::Fixup skipped instruction due to IT\n")); + pCtx->Pc = m_targetPc; + + _ASSERTE(!m_fEmulatedITInstruction); + m_originalITState.Advance(); + m_originalITState.Set(pCtx); + } + else + { + // We've hit a breakpoint in the code stream. We will return false here (which causes us to NOT + // replace the breakpoint code with single step), and place the Pc back to the original Pc. The + // debugger patch skipping code will move past this breakpoint. + LOG((LF_CORDB, LL_INFO100000, "ArmSingleStepper::Fixup emulated breakpoint\n")); + pCtx->Pc = m_originalPc; + + _ASSERTE(pCtx->Pc & THUMB_CODE); + return false; + } + } + } + else + { + bool res = TryEmulate(pCtx, m_opcodes[0], m_opcodes[1], true); + _ASSERTE(res); // We should always successfully emulate since we ran it through TryEmulate already. + + if (!m_fRedirectedPc) + pCtx->Pc = m_targetPc; + + LOG((LF_CORDB, LL_INFO100000, "ArmSingleStepper::Fixup emulated, ip = %x\n", pCtx->Pc)); + } + } + else + { + // The stepped instruction caused an exception. Reset the PC and IT state to their original values we + // cached before stepping. (We should never seen this when stepping an IT instruction which overwrites + // m_originalITState). + _ASSERTE(!m_fEmulatedITInstruction); + _ASSERTE(m_fEmulate == false); + pCtx->Pc = m_originalPc; + m_originalITState.Set(pCtx); + + LOG((LF_CORDB, LL_INFO100000, "ArmSingleStepper::Fixup hit exception pc = %x ex = %x\n", pCtx->Pc, dwExceptionCode)); + } + + _ASSERTE(pCtx->Pc & THUMB_CODE); + return true; +} + +// Count the number of bits set in a DWORD. +DWORD ArmSingleStepper::BitCount(DWORD dwValue) +{ + // There are faster implementations but speed isn't critical here. + DWORD cBits = 0; + while (dwValue) + { + cBits += dwValue & 1; + dwValue >>= 1; + } + return cBits; +} + +// Return true if the given condition (C, N, Z or V) holds in the current context. +#define GET_FLAG(pCtx, _flag) \ + ((pCtx->Cpsr & (1 << APSR_##_flag)) != 0) + +// Returns true if the current context indicates the ARM condition specified holds. +bool ArmSingleStepper::ConditionHolds(T_CONTEXT *pCtx, DWORD cond) +{ + switch (cond) + { + case 0: // EQ (Z==1) + return GET_FLAG(pCtx, Z); + case 1: // NE (Z==0) + return !GET_FLAG(pCtx, Z); + case 2: // CS (C==1) + return GET_FLAG(pCtx, C); + case 3: // CC (C==0) + return !GET_FLAG(pCtx, C); + case 4: // MI (N==1) + return GET_FLAG(pCtx, N); + case 5: // PL (N==0) + return !GET_FLAG(pCtx, N); + case 6: // VS (V==1) + return GET_FLAG(pCtx, V); + case 7: // VC (V==0) + return !GET_FLAG(pCtx, V); + case 8: // HI (C==1 && Z==0) + return GET_FLAG(pCtx, C) && !GET_FLAG(pCtx, Z); + case 9: // LS (C==0 || Z==1) + return !GET_FLAG(pCtx, C) || GET_FLAG(pCtx, Z); + case 10: // GE (N==V) + return GET_FLAG(pCtx, N) == GET_FLAG(pCtx, V); + case 11: // LT (N!=V) + return GET_FLAG(pCtx, N) != GET_FLAG(pCtx, V); + case 12: // GT (Z==0 && N==V) + return !GET_FLAG(pCtx, Z) && (GET_FLAG(pCtx, N) == GET_FLAG(pCtx, V)); + case 13: // LE (Z==1 || N!=V) + return GET_FLAG(pCtx, Z) || (GET_FLAG(pCtx, N) != GET_FLAG(pCtx, V)); + case 14: // AL + return true; + case 15: + _ASSERTE(!"Unsupported condition code: 15"); + return false; + default: +// UNREACHABLE(); + return false; + } +} + +// Get the current value of a register. PC (register 15) is always reported as the current instruction PC + 4 +// as per the ARM architecture. +DWORD ArmSingleStepper::GetReg(T_CONTEXT *pCtx, DWORD reg) +{ + _ASSERTE(reg <= 15); + + if (reg == 15) + return (m_originalPc + 4) & ~THUMB_CODE; + + return (&pCtx->R0)[reg]; +} + +// Set the current value of a register. If the PC (register 15) is set then m_fRedirectedPc is set to true. +void ArmSingleStepper::SetReg(T_CONTEXT *pCtx, DWORD reg, DWORD value) +{ + _ASSERTE(reg <= 15); + + if (reg == 15) + { + value |= THUMB_CODE; + m_fRedirectedPc = true; + } + + (&pCtx->R0)[reg] = value; +} + +// Attempt to read a 1, 2 or 4 byte value from memory, zero or sign extend it to a 4-byte value and place that +// value into the buffer pointed at by pdwResult. Returns false if attempting to read the location caused a +// fault. +bool ArmSingleStepper::GetMem(DWORD *pdwResult, DWORD_PTR pAddress, DWORD cbSize, bool fSignExtend) +{ + struct Param + { + DWORD *pdwResult; + DWORD_PTR pAddress; + DWORD cbSize; + bool fSignExtend; + bool bReturnValue; + } param; + + param.pdwResult = pdwResult; + param.pAddress = pAddress; + param.cbSize = cbSize; + param.fSignExtend = fSignExtend; + param.bReturnValue = true; + + PAL_TRY(Param *, pParam, ¶m) + { + switch (pParam->cbSize) + { + case 1: + *pParam->pdwResult = *(BYTE*)pParam->pAddress; + if (pParam->fSignExtend && (*pParam->pdwResult & 0x00000080)) + *pParam->pdwResult |= 0xffffff00; + break; + case 2: + *pParam->pdwResult = *(WORD*)pParam->pAddress; + if (pParam->fSignExtend && (*pParam->pdwResult & 0x00008000)) + *pParam->pdwResult |= 0xffff0000; + break; + case 4: + *pParam->pdwResult = *(DWORD*)pParam->pAddress; + break; + default: + UNREACHABLE(); + pParam->bReturnValue = false; + } + } + PAL_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + param.bReturnValue = false; + } + PAL_ENDTRY; + + return param.bReturnValue; +} + +// Wrapper around GetMem above that will automatically return from TryEmulate() indicating the instruction +// could not be emulated if we try to read memory and fail due to an exception. This logic works (i.e. we can +// simply return without worrying whether we've already updated the thread context) due to the fact that we +// either (a) read memory before updating any registers (the various LDR literal variants) or (b) update the +// register list before the base register in LDM-like operations (and this should therefore be an idempotent +// operation when we re-execute the instruction). If this ever changes we will have to store a copy of the +// original context we can use to revert changes (it gets even more complex if we ever have to emulate an +// instruction that writes memory). +#define GET_MEM(_result, _addr, _size, _signextend) \ + do { \ + if (!GetMem((_result), (_addr), (_size), (_signextend))) \ + return false; \ + } while (false) + +// Implements the various LDM-style multi-register load instructions (these include POP). +#define LDM(ctx, _base, _registerlist, _writeback, _ia) \ + do { \ + DWORD _pAddr = GetReg(ctx, _base); \ + if (!(_ia)) \ + _pAddr -= BitCount(_registerlist) * sizeof(void*); \ + DWORD _pStartAddr = _pAddr; \ + for (DWORD _i = 0; _i < 16; _i++) \ + { \ + if ((_registerlist) & (1 << _i)) \ + { \ + DWORD _tmpresult; \ + GET_MEM(&_tmpresult, _pAddr, 4, false); \ + SetReg(ctx, _i, _tmpresult); \ + _pAddr += sizeof(void*); \ + } \ + } \ + if (_writeback) \ + SetReg(ctx, _base, (_ia) ? _pAddr : _pStartAddr); \ + } while (false) + +// Parse the instruction whose first word is given in opcode1 (if the instruction is 32-bit TryEmulate will +// fetch the second word using the value of the PC stored in the current context). If the instruction reads or +// writes the PC or is the IT instruction then it will be emulated by updating the thread context +// appropriately and true will be returned. If the instruction is not one of those cases (or it is but we +// faulted trying to read memory during the emulation) no state is updated and false is returned instead. +bool ArmSingleStepper::TryEmulate(T_CONTEXT *pCtx, WORD opcode1, WORD opcode2, bool execute) +{ + LOG((LF_CORDB, LL_INFO100000, "ArmSingleStepper::TryEmulate(opcode=%x %x, execute=%s)\n", (DWORD)opcode1, (DWORD)opcode2, execute ? "true" : "false")); + + // Track whether instruction emulation wrote a modified PC. + m_fRedirectedPc = false; + + // Track whether we successfully emulated an instruction. If we did and we didn't modify the PC (e.g. a + // ADR instruction or a conditional branch not taken) then we'll need to explicitly set the PC to the next + // instruction (since our caller expects that whenever we return true m_pCtx->Pc holds the next + // instruction address). + bool fEmulated = false; + + if (Is32BitInstruction(opcode1)) + { + if (((opcode1 & 0xfbff) == 0xf2af) && + ((opcode2 & 0x8000) == 0x0000)) + { + // ADR.W : T2 + if (execute) + { + DWORD Rd = BitExtract(opcode2, 11, 8); + DWORD i = BitExtract(opcode1, 10, 10); + DWORD imm3 = BitExtract(opcode2, 14, 12); + DWORD imm8 = BitExtract(opcode2, 7, 0); + + SetReg(pCtx, Rd, (GetReg(pCtx, 15) & ~3) - ((i << 11) | (imm3 << 8) | imm8)); + } + + fEmulated = true; + } + else if (((opcode1 & 0xfbff) == 0xf20f) && + ((opcode2 & 0x8000) == 0x0000)) + { + // ADR.W : T3 + if (execute) + { + DWORD Rd = BitExtract(opcode2, 11, 8); + DWORD i = BitExtract(opcode1, 10, 10); + DWORD imm3 = BitExtract(opcode2, 14, 12); + DWORD imm8 = BitExtract(opcode2, 7, 0); + + SetReg(pCtx, Rd, (GetReg(pCtx, 15) & ~3) + ((i << 11) | (imm3 << 8) | imm8)); + } + + fEmulated = true; + } + else if (((opcode1 & 0xf800) == 0xf000) && + ((opcode2 & 0xd000) == 0x8000) && + ((opcode1 & 0x0380) != 0x0380)) + { + // B.W : T3 + if (execute) + { + DWORD S = BitExtract(opcode1, 10, 10); + DWORD cond = BitExtract(opcode1, 9, 6); + DWORD imm6 = BitExtract(opcode1, 5, 0); + DWORD J1 = BitExtract(opcode2, 13, 13); + DWORD J2 = BitExtract(opcode2, 11, 11); + DWORD imm11 = BitExtract(opcode2, 10, 0); + + if (ConditionHolds(pCtx, cond) && execute) + { + DWORD disp = (S ? 0xfff00000 : 0) | (J2 << 19) | (J1 << 18) | (imm6 << 12) | (imm11 << 1); + SetReg(pCtx, 15, GetReg(pCtx, 15) + disp); + } + } + + fEmulated = true; + } + else if (((opcode1 & 0xf800) == 0xf000) && + ((opcode2 & 0xd000) == 0x9000)) + { + // B.W : T4 + if (execute) + { + DWORD S = BitExtract(opcode1, 10, 10); + DWORD imm10 = BitExtract(opcode1, 9, 0); + DWORD J1 = BitExtract(opcode2, 13, 13); + DWORD J2 = BitExtract(opcode2, 11, 11); + DWORD imm11 = BitExtract(opcode2, 10, 0); + + DWORD I1 = (J1 ^ S) ^ 1; + DWORD I2 = (J2 ^ S) ^ 1; + + DWORD disp = (S ? 0xff000000 : 0) | (I1 << 23) | (I2 << 22) | (imm10 << 12) | (imm11 << 1); + SetReg(pCtx, 15, GetReg(pCtx, 15) + disp); + } + + fEmulated = true; + } + else if (((opcode1 & 0xf800) == 0xf000) && + ((opcode2 & 0xd000) == 0xd000)) + { + // BL (immediate) : T1 + if (execute) + { + DWORD S = BitExtract(opcode1, 10, 10); + DWORD imm10 = BitExtract(opcode1, 9, 0); + DWORD J1 = BitExtract(opcode2, 13, 13); + DWORD J2 = BitExtract(opcode2, 11, 11); + DWORD imm11 = BitExtract(opcode2, 10, 0); + + DWORD I1 = (J1 ^ S) ^ 1; + DWORD I2 = (J2 ^ S) ^ 1; + + SetReg(pCtx, 14, GetReg(pCtx, 15) | 1); + + DWORD disp = (S ? 0xff000000 : 0) | (I1 << 23) | (I2 << 22) | (imm10 << 12) | (imm11 << 1); + SetReg(pCtx, 15, GetReg(pCtx, 15) + disp); + } + + fEmulated = true; + } + else if (((opcode1 & 0xffd0) == 0xe890) && + ((opcode2 & 0x2000) == 0x0000)) + { + // LDM.W : T2, POP.W : T2 + if (execute) + { + DWORD W = BitExtract(opcode1, 5, 5); + DWORD Rn = BitExtract(opcode1, 3, 0); + DWORD registerList = opcode2; + + LDM(pCtx, Rn, registerList, W, true); + fEmulated = true; + } + else + { + // We should only emulate this instruction if Pc is set + if (opcode2 & (1<<15)) + fEmulated = true; + } + } + else if (((opcode1 & 0xffd0) == 0xe410) && + ((opcode2 & 0x2000) == 0x0000)) + { + // LDMDB : T1 + if (execute) + { + DWORD W = BitExtract(opcode1, 5, 5); + DWORD Rn = BitExtract(opcode1, 3, 0); + DWORD registerList = opcode2; + + LDM(pCtx, Rn, registerList, W, false); + fEmulated = true; + } + else + { + // We should only emulate this instruction if Pc is set + if (opcode2 & (1<<15)) + fEmulated = true; + } + } + else if (((opcode1 & 0xfff0) == 0xf8d0) && + ((opcode1 & 0x000f) != 0x000f)) + { + // LDR.W (immediate): T3 + DWORD Rt = BitExtract(opcode2, 15, 12); + DWORD Rn = BitExtract(opcode1, 3, 0); + if (execute) + { + DWORD imm12 = BitExtract(opcode2, 11, 0); + + DWORD value; + GET_MEM(&value, GetReg(pCtx, Rn) + imm12, 4, false); + + SetReg(pCtx, Rt, value); + fEmulated = true; + } + else + { + // We should only emulate this instruction if Pc is used + if (Rt == 15 || Rn == 15) + fEmulated = true; + } + } + else if (((opcode1 & 0xfff0) == 0xf850) && + ((opcode2 & 0x0800) == 0x0800) && + ((opcode1 & 0x000f) != 0x000f)) + { + // LDR (immediate) : T4, POP : T3 + DWORD Rn = BitExtract(opcode1, 3, 0); + DWORD Rt = BitExtract(opcode2, 15, 12); + if (execute) + { + DWORD P = BitExtract(opcode2, 10, 10); + DWORD U = BitExtract(opcode2, 9, 9); + DWORD W = BitExtract(opcode2, 8, 8); + DWORD imm8 = BitExtract(opcode2, 7, 0); + + DWORD offset_addr = U ? GetReg(pCtx, Rn) + imm8 : GetReg(pCtx, Rn) - imm8; + DWORD addr = P ? offset_addr : GetReg(pCtx, Rn); + + DWORD value; + GET_MEM(&value, addr, 4, false); + + if (W) + SetReg(pCtx, Rn, offset_addr); + + SetReg(pCtx, Rt, value); + fEmulated = true; + } + else + { + // We should only emulate this instruction if Pc is used + if (Rt == 15 || Rn == 15) + fEmulated = true; + } + } + else if (((opcode1 & 0xff7f) == 0xf85f)) + { + // LDR.W (literal) : T2 + DWORD Rt = BitExtract(opcode2, 15, 12); + if (execute) + { + DWORD U = BitExtract(opcode1, 7, 7); + DWORD imm12 = BitExtract(opcode2, 11, 0); + + // This instruction always reads relative to R15/PC + DWORD addr = GetReg(pCtx, 15) & ~3; + addr = U ? addr + imm12 : addr - imm12; + + DWORD value; + GET_MEM(&value, addr, 4, false); + + SetReg(pCtx, Rt, value); + } + + // We should ALWAYS emulate this instruction, because this instruction + // always reads the memory relative to PC + fEmulated = true; + } + else if (((opcode1 & 0xfff0) == 0xf850) && + ((opcode2 & 0x0fc0) == 0x0000) && + ((opcode1 & 0x000f) != 0x000f)) + { + // LDR.W : T2 + DWORD Rn = BitExtract(opcode1, 3, 0); + DWORD Rt = BitExtract(opcode2, 15, 12); + DWORD Rm = BitExtract(opcode2, 3, 0); + if (execute) + { + DWORD imm2 = BitExtract(opcode2, 5, 4); + DWORD addr = GetReg(pCtx, Rn) + (GetReg(pCtx, Rm) << imm2); + + DWORD value; + GET_MEM(&value, addr, 4, false); + + SetReg(pCtx, Rt, value); + fEmulated = true; + } + else + { + // We should only emulate this instruction if Pc is used + if (Rt == 15 || Rn == 15 || Rm == 15) + fEmulated = true; + } + } + else if (((opcode1 & 0xff7f) == 0xf81f) && + ((opcode2 & 0xf000) != 0xf000)) + { + // LDRB (literal) : T2 + if (execute) + { + DWORD U = BitExtract(opcode1, 7, 7); + DWORD Rt = BitExtract(opcode2, 15, 12); + DWORD imm12 = BitExtract(opcode2, 11, 0); + + DWORD addr = (GetReg(pCtx, 15) & ~3); + addr = U ? addr + imm12 : addr - imm12; + + DWORD value; + GET_MEM(&value, addr, 1, false); + + SetReg(pCtx, Rt, value); + } + + fEmulated = true; + } + else if (((opcode1 & 0xfe5f) == 0xe85f) && + ((opcode1 & 0x0120) != 0x0000)) + { + // LDRD (literal) : T1 + if (execute) + { + DWORD U = BitExtract(opcode1, 7, 7); + DWORD Rt = BitExtract(opcode2, 15, 12); + DWORD Rt2 = BitExtract(opcode2, 11, 8); + DWORD imm8 = BitExtract(opcode2, 7, 0); + + DWORD addr = (GetReg(pCtx, 15) & ~3); + addr = U ? addr + (imm8 << 2) : addr - (imm8 << 2); + + DWORD value1; + GET_MEM(&value1, addr, 4, false); + + DWORD value2; + GET_MEM(&value2, addr + 4, 4, false); + + SetReg(pCtx, Rt, value1); + SetReg(pCtx, Rt2, value2); + } + + fEmulated = true; + } + else if (((opcode1 & 0xff7f) == 0xf83f) && + ((opcode2 & 0xf000) != 0xf000)) + { + // LDRH (literal) : T1 + if (execute) + { + DWORD U = BitExtract(opcode1, 7, 7); + DWORD Rt = BitExtract(opcode2, 15, 12); + DWORD imm12 = BitExtract(opcode2, 11, 0); + + DWORD addr = (GetReg(pCtx, 15) & ~3); + addr = U ? addr + imm12 : addr - imm12; + + DWORD value; + GET_MEM(&value, addr, 2, false); + + SetReg(pCtx, Rt, value); + } + + fEmulated = true; + } + else if (((opcode1 & 0xff7f) == 0xf91f) && + ((opcode2 & 0xf000) != 0xf000)) + { + // LDRSB (literal) : T1 + if (execute) + { + DWORD U = BitExtract(opcode1, 7, 7); + DWORD Rt = BitExtract(opcode2, 15, 12); + DWORD imm12 = BitExtract(opcode2, 11, 0); + + DWORD addr = (GetReg(pCtx, 15) & ~3); + addr = U ? addr + imm12 : addr - imm12; + + DWORD value; + GET_MEM(&value, addr, 1, true); + + SetReg(pCtx, Rt, value); + } + + fEmulated = true; + } + else if (((opcode1 & 0xff7f) == 0xf53f) && + ((opcode2 & 0xf000) != 0xf000)) + { + // LDRSH (literal) : T1 + if (execute) + { + DWORD U = BitExtract(opcode1, 7, 7); + DWORD Rt = BitExtract(opcode2, 15, 12); + DWORD imm12 = BitExtract(opcode2, 11, 0); + + DWORD addr = (GetReg(pCtx, 15) & ~3); + addr = U ? addr + imm12 : addr - imm12; + + DWORD value; + GET_MEM(&value, addr, 2, true); + + SetReg(pCtx, Rt, value); + } + + fEmulated = true; + } + else if (((opcode1 & 0xfff0) == 0xe8d0) && + ((opcode2 & 0xffe0) == 0xf000)) + { + // TBB/TBH : T1 + if (execute) + { + DWORD Rn = BitExtract(opcode1, 3, 0); + DWORD H = BitExtract(opcode2, 4, 4); + DWORD Rm = BitExtract(opcode2, 3, 0); + + DWORD addr = GetReg(pCtx, Rn); + + DWORD value; + if (H) + GET_MEM(&value, addr + (GetReg(pCtx, Rm) << 1), 2, false); + else + GET_MEM(&value, addr + GetReg(pCtx, Rm), 1, false); + + SetReg(pCtx, 15, GetReg(pCtx, 15) + (value << 1)); + } + + fEmulated = true; + } + + // If we emulated an instruction but didn't set the PC explicitly we have to do so now (in such cases + // the next PC will always point directly after the instruction we just emulated). + if (fEmulated && !m_fRedirectedPc) + SetReg(pCtx, 15, GetReg(pCtx, 15)); + } + else + { + // Handle 16-bit instructions. + + if ((opcode1 & 0xf800) == 0xa000) + { + // ADR : T1 + if (execute) + { + DWORD Rd = BitExtract(opcode1, 10, 8); + DWORD imm8 = BitExtract(opcode1, 7, 0); + + SetReg(pCtx, Rd, (GetReg(pCtx, 15) & 3) + (imm8 << 2)); + } + + fEmulated = true; + } + else if (((opcode1 & 0xf000) == 0xd000) && ((opcode1 & 0x0f00) != 0x0e00)) + { + // B : T1 + + // We only emulate this instruction if we take the conditional + // jump. If not we'll pass right over the jump and set the + // target IP as normal. + DWORD cond = BitExtract(opcode1, 11, 8); + if (execute) + { + _ASSERTE(ConditionHolds(pCtx, cond)); + + DWORD imm8 = BitExtract(opcode1, 7, 0); + DWORD disp = (imm8 << 1) | ((imm8 & 0x80) ? 0xffffff00 : 0); + + SetReg(pCtx, 15, GetReg(pCtx, 15) + disp); + fEmulated = true; + } + else + { + if (ConditionHolds(pCtx, cond)) + { + fEmulated = true; + } + } + } + else if ((opcode1 & 0xf800) == 0xe000) + { + if (execute) + { + // B : T2 + DWORD imm11 = BitExtract(opcode1, 10, 0); + DWORD disp = (imm11 << 1) | ((imm11 & 0x400) ? 0xfffff000 : 0); + + SetReg(pCtx, 15, GetReg(pCtx, 15) + disp); + } + + fEmulated = true; + } + else if ((opcode1 & 0xff87) == 0x4780) + { + // BLX (register) : T1 + if (execute) + { + DWORD Rm = BitExtract(opcode1, 6, 3); + DWORD addr = GetReg(pCtx, Rm); + + SetReg(pCtx, 14, (GetReg(pCtx, 15) - 2) | 1); + SetReg(pCtx, 15, addr); + } + + fEmulated = true; + } + else if ((opcode1 & 0xff87) == 0x4700) + { + // BX : T1 + if (execute) + { + DWORD Rm = BitExtract(opcode1, 6, 3); + SetReg(pCtx, 15, GetReg(pCtx, Rm)); + } + + fEmulated = true; + } + else if ((opcode1 & 0xf500) == 0xb100) + { + // CBNZ/CBZ : T1 + if (execute) + { + DWORD op = BitExtract(opcode1, 11, 11); + DWORD i = BitExtract(opcode1, 9, 9); + DWORD imm5 = BitExtract(opcode1, 7, 3); + DWORD Rn = BitExtract(opcode1, 2, 0); + + if ((op && (GetReg(pCtx, Rn) != 0)) || + (!op && (GetReg(pCtx, Rn) == 0))) + { + SetReg(pCtx, 15, GetReg(pCtx, 15) + ((i << 6) | (imm5 << 1))); + } + } + + fEmulated = true; + } + else if (((opcode1 & 0xff00) == 0xbf00) && + ((opcode1 & 0x000f) != 0x0000)) + { + // IT : T1 + if (execute) + { + DWORD firstcond = BitExtract(opcode1, 7, 4); + DWORD mask = BitExtract(opcode1, 3, 0); + + // The IT instruction is special. We compute the IT state bits for the CPSR and cache them in + // m_originalITState. We then set m_fEmulatedITInstruction so that Fixup() knows not to advance + // this state (simply write it as-is back into the CPSR). + m_originalITState.Init((BYTE)((firstcond << 4) | mask)); + m_originalITState.Set(pCtx); + m_fEmulatedITInstruction = true; + } + + fEmulated = true; + } + else if ((opcode1 & 0xf800) == 0x4800) + { + // LDR (literal) : T1 + if (execute) + { + DWORD Rt = BitExtract(opcode1, 10, 8); + DWORD imm8 = BitExtract(opcode1, 7, 0); + + DWORD addr = (GetReg(pCtx, 15) & ~3) + (imm8 << 2); + + DWORD value = 0; + GET_MEM(&value, addr, 4, false); + + SetReg(pCtx, Rt, value); + } + + fEmulated = true; + } + else if ((opcode1 & 0xff00) == 0x4600) + { + // MOV (register) : T1 + DWORD D = BitExtract(opcode1, 7, 7); + DWORD Rm = BitExtract(opcode1, 6, 3); + DWORD Rd = (D << 3) | BitExtract(opcode1, 2, 0); + + if (execute) + { + SetReg(pCtx, Rd, GetReg(pCtx, Rm)); + fEmulated = true; + } + else + { + // Only emulate if we change Pc + if (Rm == 15 || Rd == 15) + fEmulated = true; + } + } + else if ((opcode1 & 0xfe00) == 0xbc00) + { + // POP : T1 + DWORD P = BitExtract(opcode1, 8, 8); + DWORD registerList = (P << 15) | BitExtract(opcode1, 7, 0); + if (execute) + { + LDM(pCtx, 13, registerList, true, true); + fEmulated = true; + } + else + { + // Only emulate if Pc is in the register list + if (registerList & (1<<15)) + fEmulated = true; + } + } + + // If we emulated an instruction but didn't set the PC explicitly we have to do so now (in such cases + // the next PC will always point directly after the instruction we just emulated). + if (execute && fEmulated && !m_fRedirectedPc) + SetReg(pCtx, 15, GetReg(pCtx, 15) - 2); + } + + LOG((LF_CORDB, LL_INFO100000, "ArmSingleStepper::TryEmulate(opcode=%x %x) emulated=%s redirectedPc=%s\n", + (DWORD)opcode1, (DWORD)opcode2, fEmulated ? "true" : "false", m_fRedirectedPc ? "true" : "false")); + return fEmulated; +} diff --git a/src/vm/arm/asmconstants.h b/src/vm/arm/asmconstants.h new file mode 100644 index 0000000000..47ebb2d24d --- /dev/null +++ b/src/vm/arm/asmconstants.h @@ -0,0 +1,304 @@ +// 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. +// asmconstants.h - +// +// This header defines field offsets and constants used by assembly code +// Be sure to rebuild clr/src/vm/ceemain.cpp after changing this file, to +// ensure that the constants match the expected C/C++ values + +// #ifndef _ARM_ +// #error this file should only be used on an ARM platform +// #endif // _ARM_ + +#include "../../inc/switches.h" + +//----------------------------------------------------------------------------- + +#ifndef ASMCONSTANTS_C_ASSERT +#define ASMCONSTANTS_C_ASSERT(cond) +#endif + +#ifndef ASMCONSTANTS_RUNTIME_ASSERT +#define ASMCONSTANTS_RUNTIME_ASSERT(cond) +#endif + +// Some contants are different in _DEBUG builds. This macro factors out ifdefs from below. +#ifdef _DEBUG +#define DBG_FRE(dbg,fre) dbg +#else +#define DBG_FRE(dbg,fre) fre +#endif + +#define DynamicHelperFrameFlags_Default 0 +#define DynamicHelperFrameFlags_ObjectArg 1 +#define DynamicHelperFrameFlags_ObjectArg2 2 + +#define REDIRECTSTUB_SP_OFFSET_CONTEXT 0 + +#define CORINFO_NullReferenceException_ASM 0 +ASMCONSTANTS_C_ASSERT( CORINFO_NullReferenceException_ASM + == CORINFO_NullReferenceException); + +#define CORINFO_IndexOutOfRangeException_ASM 3 +ASMCONSTANTS_C_ASSERT( CORINFO_IndexOutOfRangeException_ASM + == CORINFO_IndexOutOfRangeException); + + +// Offset of the array containing the address of captured registers in MachState +#define MachState__captureR4_R11 0x0 +ASMCONSTANTS_C_ASSERT(MachState__captureR4_R11 == offsetof(MachState, captureR4_R11)) + +// Offset of the array containing the address of preserved registers in MachState +#define MachState___R4_R11 0x20 +ASMCONSTANTS_C_ASSERT(MachState___R4_R11 == offsetof(MachState, _R4_R11)) + +#define MachState__isValid 0x48 +ASMCONSTANTS_C_ASSERT(MachState__isValid == offsetof(MachState, _isValid)) + +#define LazyMachState_captureR4_R11 MachState__captureR4_R11 +ASMCONSTANTS_C_ASSERT(LazyMachState_captureR4_R11 == offsetof(LazyMachState, captureR4_R11)) + +#define LazyMachState_captureSp (MachState__isValid+4) +ASMCONSTANTS_C_ASSERT(LazyMachState_captureSp == offsetof(LazyMachState, captureSp)) + +#define LazyMachState_captureIp (LazyMachState_captureSp+4) +ASMCONSTANTS_C_ASSERT(LazyMachState_captureIp == offsetof(LazyMachState, captureIp)) + +#define DelegateObject___methodPtr 0x0c +ASMCONSTANTS_C_ASSERT(DelegateObject___methodPtr == offsetof(DelegateObject, _methodPtr)); + +#define DelegateObject___target 0x04 +ASMCONSTANTS_C_ASSERT(DelegateObject___target == offsetof(DelegateObject, _target)); + +#define MethodTable__m_BaseSize 0x04 +ASMCONSTANTS_C_ASSERT(MethodTable__m_BaseSize == offsetof(MethodTable, m_BaseSize)); + +#define MethodTable__m_dwFlags 0x0 +ASMCONSTANTS_C_ASSERT(MethodTable__m_dwFlags == offsetof(MethodTable, m_dwFlags)); + +#define MethodTable__m_pWriteableData DBG_FRE(0x1c, 0x18) +ASMCONSTANTS_C_ASSERT(MethodTable__m_pWriteableData == offsetof(MethodTable, m_pWriteableData)); + +#define MethodTable__enum_flag_ContainsPointers 0x01000000 +ASMCONSTANTS_C_ASSERT(MethodTable__enum_flag_ContainsPointers == MethodTable::enum_flag_ContainsPointers); + +#define MethodTable__m_ElementType DBG_FRE(0x24, 0x20) +ASMCONSTANTS_C_ASSERT(MethodTable__m_ElementType == offsetof(MethodTable, m_pMultipurposeSlot1)); + +#define SIZEOF__MethodTable DBG_FRE(0x2c, 0x28) +ASMCONSTANTS_C_ASSERT(SIZEOF__MethodTable == sizeof(MethodTable)); + +#define MethodTableWriteableData__m_dwFlags 0x00 +ASMCONSTANTS_C_ASSERT(MethodTableWriteableData__m_dwFlags == offsetof(MethodTableWriteableData, m_dwFlags)); + +#define MethodTableWriteableData__enum_flag_Unrestored 0x04 +ASMCONSTANTS_C_ASSERT(MethodTableWriteableData__enum_flag_Unrestored == MethodTableWriteableData::enum_flag_Unrestored); + +#define StringObject__m_StringLength 0x04 +ASMCONSTANTS_C_ASSERT(StringObject__m_StringLength == offsetof(StringObject, m_StringLength)); + +#define SIZEOF__BaseStringObject 0xe +ASMCONSTANTS_C_ASSERT(SIZEOF__BaseStringObject == (ObjSizeOf(StringObject) + sizeof(WCHAR))); + +#define SIZEOF__ArrayOfObjectRef 0xc +ASMCONSTANTS_C_ASSERT(SIZEOF__ArrayOfObjectRef == ObjSizeOf(ArrayBase)); + +#define SIZEOF__ArrayOfValueType 0xc +ASMCONSTANTS_C_ASSERT(SIZEOF__ArrayOfValueType == ObjSizeOf(ArrayBase)); + +#define ArrayBase__m_NumComponents 0x4 +ASMCONSTANTS_C_ASSERT(ArrayBase__m_NumComponents == offsetof(ArrayBase, m_NumComponents)); + +#define ArrayTypeDesc__m_TemplateMT 0x4 +ASMCONSTANTS_C_ASSERT(ArrayTypeDesc__m_TemplateMT == offsetof(ArrayTypeDesc, m_TemplateMT)); + +#define ArrayTypeDesc__m_Arg 0x8 +ASMCONSTANTS_C_ASSERT(ArrayTypeDesc__m_Arg == offsetof(ArrayTypeDesc, m_Arg)); + +#define PtrArray__m_Array 0x8 +ASMCONSTANTS_C_ASSERT(PtrArray__m_Array == offsetof(PtrArray, m_Array)); + +#define SYSTEM_INFO__dwNumberOfProcessors 0x14 +ASMCONSTANTS_C_ASSERT(SYSTEM_INFO__dwNumberOfProcessors == offsetof(SYSTEM_INFO, dwNumberOfProcessors)); + +#define TypeHandle_CanCast 0x1 // TypeHandle::CanCast + +// Maximum number of characters to be allocated for a string in AllocateStringFast*. Chosen so that we'll +// never have to check for overflow and will never try to allocate a string on regular heap that should have +// gone on the large object heap. Additionally the constant has been chosen such that it can be encoded in a +// single Thumb2 CMP instruction. +#define MAX_FAST_ALLOCATE_STRING_SIZE 42240 +ASMCONSTANTS_C_ASSERT(MAX_FAST_ALLOCATE_STRING_SIZE < ((LARGE_OBJECT_SIZE - SIZEOF__BaseStringObject) / 2)); + + +// Array of objectRef of this Maximum number of elements can be allocated in JIT_NewArr1OBJ_MP*. Chosen so that we'll +// never have to check for overflow and will never try to allocate the array on regular heap that should have +// gone on the large object heap. Additionally the constant has been chosen such that it can be encoded in a +// single Thumb2 CMP instruction. +#define MAX_FAST_ALLOCATE_ARRAY_OBJECTREF_SIZE 21120 +ASMCONSTANTS_C_ASSERT(MAX_FAST_ALLOCATE_ARRAY_OBJECTREF_SIZE < ((LARGE_OBJECT_SIZE - SIZEOF__ArrayOfObjectRef) / sizeof(void*))); + +// Array of valueClass of this Maximum number of characters can be allocated JIT_NewArr1VC_MP*. Chosen so that we'll +// never have to check for overflow and will never try to allocate the array on regular heap that should have +// gone on the large object heap. Additionally the constant has been chosen such that it can be encoded in a +// single Thumb2 CMP instruction. +#define MAX_FAST_ALLOCATE_ARRAY_VC_SIZE 65280 +ASMCONSTANTS_C_ASSERT(MAX_FAST_ALLOCATE_ARRAY_VC_SIZE < ((4294967296 - 1 - SIZEOF__ArrayOfValueType) / 65536)); + +#define SIZEOF__GSCookie 0x4 +ASMCONSTANTS_C_ASSERT(SIZEOF__GSCookie == sizeof(GSCookie)); + +#define SIZEOF__Frame 0x8 +ASMCONSTANTS_C_ASSERT(SIZEOF__Frame == sizeof(Frame)); + +#define SIZEOF__CONTEXT 0x1a0 +ASMCONSTANTS_C_ASSERT(SIZEOF__CONTEXT == sizeof(T_CONTEXT)); + +#define SIZEOF__CalleeSavedRegisters 0x24 +ASMCONSTANTS_C_ASSERT(SIZEOF__CalleeSavedRegisters == sizeof(CalleeSavedRegisters)) + +#define SIZEOF__ArgumentRegisters 0x10 +ASMCONSTANTS_C_ASSERT(SIZEOF__ArgumentRegisters == sizeof(ArgumentRegisters)) + +#define SIZEOF__FloatArgumentRegisters 0x40 +ASMCONSTANTS_C_ASSERT(SIZEOF__FloatArgumentRegisters == sizeof(FloatArgumentRegisters)) + +#define UMEntryThunk__m_pUMThunkMarshInfo 0x0C +ASMCONSTANTS_C_ASSERT(UMEntryThunk__m_pUMThunkMarshInfo == offsetof(UMEntryThunk, m_pUMThunkMarshInfo)) + +#define UMEntryThunk__m_dwDomainId 0x10 +ASMCONSTANTS_C_ASSERT(UMEntryThunk__m_dwDomainId == offsetof(UMEntryThunk, m_dwDomainId)) + +#define UMThunkMarshInfo__m_pILStub 0x00 +ASMCONSTANTS_C_ASSERT(UMThunkMarshInfo__m_pILStub == offsetof(UMThunkMarshInfo, m_pILStub)) + +#define UMThunkMarshInfo__m_cbActualArgSize 0x04 +ASMCONSTANTS_C_ASSERT(UMThunkMarshInfo__m_cbActualArgSize == offsetof(UMThunkMarshInfo, m_cbActualArgSize)) + +#ifdef FEATURE_REMOTING + +#define TransparentProxyObject___stubData 0x8 +ASMCONSTANTS_C_ASSERT(TransparentProxyObject___stubData == offsetof(TransparentProxyObject, _stubData)) + +#define TransparentProxyObject___stub 0x14 +ASMCONSTANTS_C_ASSERT(TransparentProxyObject___stub == offsetof(TransparentProxyObject, _stub)) + +#define TransparentProxyObject___pMT 0xc +ASMCONSTANTS_C_ASSERT(TransparentProxyObject___pMT == offsetof(TransparentProxyObject, _pMT)) + +#define RemotingPrecode__m_pMethodDesc 0x10 +ASMCONSTANTS_C_ASSERT(RemotingPrecode__m_pMethodDesc == offsetof(RemotingPrecode, m_pMethodDesc)) + +#define REMOTING_PRECODE_RET_OFFSET 0x06 + +#endif // FEATURE_REMOTING + +#define MethodDesc__m_wFlags DBG_FRE(0x1A, 0x06) +ASMCONSTANTS_C_ASSERT(MethodDesc__m_wFlags == offsetof(MethodDesc, m_wFlags)) + +#define MethodDesc__mdcClassification 0x7 +ASMCONSTANTS_C_ASSERT(MethodDesc__mdcClassification == mdcClassification) + +#ifdef FEATURE_COMINTEROP + +#define MethodDesc__mcComInterop 0x6 +ASMCONSTANTS_C_ASSERT(MethodDesc__mcComInterop == mcComInterop) + +#define Stub__m_pCode DBG_FRE(0x10, 0x0c) +ASMCONSTANTS_C_ASSERT(Stub__m_pCode == sizeof(Stub)) + +#define SIZEOF__ComMethodFrame 0x24 +ASMCONSTANTS_C_ASSERT(SIZEOF__ComMethodFrame == sizeof(ComMethodFrame)) + +#define UnmanagedToManagedFrame__m_pvDatum 0x08 +ASMCONSTANTS_C_ASSERT(UnmanagedToManagedFrame__m_pvDatum == offsetof(UnmanagedToManagedFrame, m_pvDatum)) + +// In ComCallPreStub and GenericComPlusCallStub, we setup R12 to contain address of ComCallMethodDesc after doing the following: +// +// mov r12, pc +// +// This constant defines where ComCallMethodDesc is post execution of the above instruction. +#define ComCallMethodDesc_Offset_FromR12 0x8 + +#endif // FEATURE_COMINTEROP + +#ifndef CROSSGEN_COMPILE +#define Thread__m_alloc_context__alloc_limit 0x44 +ASMCONSTANTS_C_ASSERT(Thread__m_alloc_context__alloc_limit == offsetof(Thread, m_alloc_context) + offsetof(alloc_context, alloc_limit)); + +#define Thread__m_alloc_context__alloc_ptr 0x40 +ASMCONSTANTS_C_ASSERT(Thread__m_alloc_context__alloc_ptr == offsetof(Thread, m_alloc_context) + offsetof(alloc_context, alloc_ptr)); +#endif // CROSSGEN_COMPILE + +#define Thread__m_fPreemptiveGCDisabled 0x08 +#ifndef CROSSGEN_COMPILE +ASMCONSTANTS_C_ASSERT(Thread__m_fPreemptiveGCDisabled == offsetof(Thread, m_fPreemptiveGCDisabled)); +#endif // CROSSGEN_COMPILE +#define Thread_m_fPreemptiveGCDisabled Thread__m_fPreemptiveGCDisabled + +#define Thread__m_pFrame 0x0C +#ifndef CROSSGEN_COMPILE +ASMCONSTANTS_C_ASSERT(Thread__m_pFrame == offsetof(Thread, m_pFrame)); +#endif // CROSSGEN_COMPILE +#define Thread_m_pFrame Thread__m_pFrame + +#ifndef CROSSGEN_COMPILE +#define Thread__m_pDomain 0x14 +ASMCONSTANTS_C_ASSERT(Thread__m_pDomain == offsetof(Thread, m_pDomain)); + +#define AppDomain__m_dwId 0x04 +ASMCONSTANTS_C_ASSERT(AppDomain__m_dwId == offsetof(AppDomain, m_dwId)); + +#define AppDomain__m_sDomainLocalBlock 0x08 +ASMCONSTANTS_C_ASSERT(AppDomain__m_sDomainLocalBlock == offsetof(AppDomain, m_sDomainLocalBlock)); + +#define DomainLocalBlock__m_pModuleSlots 0x04 +ASMCONSTANTS_C_ASSERT(DomainLocalBlock__m_pModuleSlots == offsetof(DomainLocalBlock, m_pModuleSlots)); + +#define DomainLocalModule__m_pDataBlob 0x18 +ASMCONSTANTS_C_ASSERT(DomainLocalModule__m_pDataBlob == offsetof(DomainLocalModule, m_pDataBlob)); + +#define DomainLocalModule__m_pGCStatics 0x10 +ASMCONSTANTS_C_ASSERT(DomainLocalModule__m_pGCStatics == offsetof(DomainLocalModule, m_pGCStatics)); + +#endif + +#define ASM__VTABLE_SLOTS_PER_CHUNK 8 +ASMCONSTANTS_C_ASSERT(ASM__VTABLE_SLOTS_PER_CHUNK == VTABLE_SLOTS_PER_CHUNK) + +#define ASM__VTABLE_SLOTS_PER_CHUNK_LOG2 3 +ASMCONSTANTS_C_ASSERT(ASM__VTABLE_SLOTS_PER_CHUNK_LOG2 == VTABLE_SLOTS_PER_CHUNK_LOG2) + +#define VASigCookie__pNDirectILStub 0x4 +ASMCONSTANTS_C_ASSERT(VASigCookie__pNDirectILStub == offsetof(VASigCookie, pNDirectILStub)) + +#define CONTEXT_Pc 0x040 +ASMCONSTANTS_C_ASSERT(CONTEXT_Pc == offsetof(T_CONTEXT,Pc)) + +#define TLS_GETTER_MAX_SIZE_ASM 0x10 +ASMCONSTANTS_C_ASSERT(TLS_GETTER_MAX_SIZE_ASM == TLS_GETTER_MAX_SIZE) + +#define CallDescrData__pSrc 0x00 +#define CallDescrData__numStackSlots 0x04 +#define CallDescrData__pArgumentRegisters 0x08 +#define CallDescrData__pFloatArgumentRegisters 0x0C +#define CallDescrData__fpReturnSize 0x10 +#define CallDescrData__pTarget 0x14 +#define CallDescrData__returnValue 0x18 + +ASMCONSTANTS_C_ASSERT(CallDescrData__pSrc == offsetof(CallDescrData, pSrc)) +ASMCONSTANTS_C_ASSERT(CallDescrData__numStackSlots == offsetof(CallDescrData, numStackSlots)) +ASMCONSTANTS_C_ASSERT(CallDescrData__pArgumentRegisters == offsetof(CallDescrData, pArgumentRegisters)) +ASMCONSTANTS_C_ASSERT(CallDescrData__pFloatArgumentRegisters == offsetof(CallDescrData, pFloatArgumentRegisters)) +ASMCONSTANTS_C_ASSERT(CallDescrData__fpReturnSize == offsetof(CallDescrData, fpReturnSize)) +ASMCONSTANTS_C_ASSERT(CallDescrData__pTarget == offsetof(CallDescrData, pTarget)) +ASMCONSTANTS_C_ASSERT(CallDescrData__returnValue == offsetof(CallDescrData, returnValue)) + +#define SIZEOF__FaultingExceptionFrame (SIZEOF__Frame + 0x8 + SIZEOF__CONTEXT) +#define FaultingExceptionFrame__m_fFilterExecuted SIZEOF__Frame +ASMCONSTANTS_C_ASSERT(SIZEOF__FaultingExceptionFrame == sizeof(FaultingExceptionFrame)); +ASMCONSTANTS_C_ASSERT(FaultingExceptionFrame__m_fFilterExecuted == offsetof(FaultingExceptionFrame, m_fFilterExecuted)); + +#undef ASMCONSTANTS_RUNTIME_ASSERT +#undef ASMCONSTANTS_C_ASSERT diff --git a/src/vm/arm/asmhelpers.S b/src/vm/arm/asmhelpers.S new file mode 100644 index 0000000000..65dc513cce --- /dev/null +++ b/src/vm/arm/asmhelpers.S @@ -0,0 +1,1474 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== +#include "unixasmmacros.inc" +#include "asmconstants.h" + +.syntax unified +.thumb + +// LPVOID __stdcall GetCurrentIP(void)// + LEAF_ENTRY GetCurrentIP, _TEXT + mov r0, lr + bx lr + LEAF_END GetCurrentIP, _TEXT + +// LPVOID __stdcall GetCurrentSP(void)// + LEAF_ENTRY GetCurrentSP, _TEXT + mov r0, sp + bx lr + LEAF_END GetCurrentSP, _TEXT + +//----------------------------------------------------------------------------- +// This helper routine enregisters the appropriate arguments and makes the +// actual call. +//----------------------------------------------------------------------------- +//void CallDescrWorkerInternal(CallDescrData * pCallDescrData)// + NESTED_ENTRY CallDescrWorkerInternal,_TEXT,NoHandler + PROLOG_PUSH "{r4,r5,r7,lr}" + PROLOG_STACK_SAVE_OFFSET r7, #8 + + mov r5,r0 // save pCallDescrData in r5 + + ldr r1, [r5,#CallDescrData__numStackSlots] + cbz r1, LOCAL_LABEL(Ldonestack) + + // Add frame padding to ensure frame size is a multiple of 8 (a requirement of the OS ABI). + // We push four registers (above) and numStackSlots arguments (below). If this comes to an odd number + // of slots we must pad with another. This simplifies to "if the low bit of numStackSlots is set, + // extend the stack another four bytes". + lsls r2, r1, #2 + and r3, r2, #4 + sub sp, sp, r3 + + // This loop copies numStackSlots words + // from [pSrcEnd-4,pSrcEnd-8,...] to [sp-4,sp-8,...] + ldr r0, [r5,#CallDescrData__pSrc] + add r0,r0,r2 +LOCAL_LABEL(Lstackloop): + ldr r2, [r0,#-4]! + str r2, [sp,#-4]! + subs r1, r1, #1 + bne LOCAL_LABEL(Lstackloop) +LOCAL_LABEL(Ldonestack): + + // If FP arguments are supplied in registers (r3 != NULL) then initialize all of them from the pointer + // given in r3. Do not use "it" since it faults in floating point even when the instruction is not executed. + ldr r3, [r5,#CallDescrData__pFloatArgumentRegisters] + cbz r3, LOCAL_LABEL(LNoFloatingPoint) + vldm r3, {s0-s15} +LOCAL_LABEL(LNoFloatingPoint): + + // Copy [pArgumentRegisters, ..., pArgumentRegisters + 12] + // into r0, ..., r3 + + ldr r4, [r5,#CallDescrData__pArgumentRegisters] + ldm r4, {r0-r3} + + CHECK_STACK_ALIGNMENT + + // call pTarget + // Note that remoting expect target in r4. + ldr r4, [r5,#CallDescrData__pTarget] + blx r4 + + ldr r3, [r5,#CallDescrData__fpReturnSize] + + // Save FP return value if appropriate + cbz r3, LOCAL_LABEL(LFloatingPointReturnDone) + + // Float return case + // Do not use "it" since it faults in floating point even when the instruction is not executed. + cmp r3, #4 + bne LOCAL_LABEL(LNoFloatReturn) + vmov r0, s0 + b LOCAL_LABEL(LFloatingPointReturnDone) +LOCAL_LABEL(LNoFloatReturn): + + // Double return case + // Do not use "it" since it faults in floating point even when the instruction is not executed. + cmp r3, #8 + bne LOCAL_LABEL(LNoDoubleReturn) + vmov r0, r1, s0, s1 + b LOCAL_LABEL(LFloatingPointReturnDone) +LOCAL_LABEL(LNoDoubleReturn): + + add r2, r5, #CallDescrData__returnValue + + cmp r3, #16 + bne LOCAL_LABEL(LNoFloatHFAReturn) + vstm r2, {s0-s3} + b LOCAL_LABEL(LReturnDone) +LOCAL_LABEL(LNoFloatHFAReturn): + + cmp r3, #32 + bne LOCAL_LABEL(LNoDoubleHFAReturn) + vstm r2, {d0-d3} + b LOCAL_LABEL(LReturnDone) +LOCAL_LABEL(LNoDoubleHFAReturn): + + EMIT_BREAKPOINT // Unreachable + +LOCAL_LABEL(LFloatingPointReturnDone): + + // Save return value into retbuf + str r0, [r5, #(CallDescrData__returnValue + 0)] + str r1, [r5, #(CallDescrData__returnValue + 4)] + +LOCAL_LABEL(LReturnDone): + +#ifdef _DEBUG + // trash the floating point registers to ensure that the HFA return values + // won't survive by accident + vldm sp, {d0-d3} +#endif + + EPILOG_STACK_RESTORE_OFFSET r7, #8 + EPILOG_POP "{r4,r5,r7,pc}" + + NESTED_END CallDescrWorkerInternal,_TEXT + + +//----------------------------------------------------------------------------- +// This helper routine is where returns for irregular tail calls end up +// so they can dynamically pop their stack arguments. +//----------------------------------------------------------------------------- +// +// Stack Layout (stack grows up, 0 at the top, offsets relative to frame pointer, r7): +// +// sp -> callee stack arguments +// : +// : +// -0Ch gsCookie +// TailCallHelperFrame -> +// -08h __VFN_table +// -04h m_Next +// r7 -> +// +00h m_calleeSavedRgisters.r4 +// +04h .r5 +// +08h .r6 +// +0Ch .r7 +// +10h .r8 +// +14h .r9 +// +18h .r10 +// r11-> +// +1Ch .r11 +// +20h .r14 -or- m_ReturnAddress +// +// r6 -> GetThread() +// r5 -> r6->m_pFrame (old Frame chain head) +// r11 is used to preserve the ETW call stack + + NESTED_ENTRY TailCallHelperStub, _TEXT, NoHandler + // + // This prolog is never executed, but we keep it here for reference + // and for the unwind data it generates + // + + // Spill callee saved registers and return address. + PROLOG_PUSH "{r4-r11,lr}" + + PROLOG_STACK_SAVE_OFFSET r7, #12 + + // + // This is the code that would have to run to setup this frame + // like the C++ helper does before calling RtlRestoreContext + // + // Allocate space for the rest of the frame and GSCookie. + // PROLOG_STACK_ALLOC 0x0C + // + // Set r11 for frame chain + //add r11, r7, 0x1C + // + // Set the vtable for TailCallFrame + //bl TCF_GETMETHODFRAMEVPTR + //str r0, [r7, #-8] + // + // Initialize the GSCookie within the Frame + //ldr r0, =s_gsCookie + //str r0, [r7, #-0x0C] + // + // Link the TailCallFrameinto the Frame chain + // and initialize r5 & r6 for unlinking later + //CALL_GETTHREAD + //mov r6, r0 + //ldr r5, [r6, #Thread__m_pFrame] + //str r5, [r7, #-4] + //sub r0, r7, 8 + //str r0, [r6, #Thread__m_pFrame] + // + // None of the previous stuff is ever executed, + // but we keep it here for reference + // + + // + // Here's the pretend call (make it real so the unwinder + // doesn't think we're in the prolog) + // + bl C_FUNC(TailCallHelperStub) + // + // with the real return address pointing to this real epilog + // +C_FUNC(JIT_TailCallHelperStub_ReturnAddress): +.global C_FUNC(JIT_TailCallHelperStub_ReturnAddress) + + // + // Our epilog (which also unlinks the StubHelperFrame) + // Be careful not to trash the return registers + // + +#ifdef _DEBUG + ldr r3, =s_gsCookie + ldr r3, [r3] + ldr r2, [r7, #-0x0C] + cmp r2, r3 + beq LOCAL_LABEL(GoodGSCookie) + bl C_FUNC(DoJITFailFast) +LOCAL_LABEL(GoodGSCookie): +#endif // _DEBUG + + // + // unlink the TailCallFrame + // + str r5, [r6, #Thread__m_pFrame] + + // + // epilog + // + EPILOG_STACK_RESTORE_OFFSET r7, #12 + EPILOG_POP "{r4-r11,lr}" + bx lr + + NESTED_END TailCallHelperStub, _TEXT + +// ------------------------------------------------------------------ + +// void LazyMachStateCaptureState(struct LazyMachState *pState)// + LEAF_ENTRY LazyMachStateCaptureState, _TEXT + + // marks that this is not yet valid + mov r1, #0 + str r1, [r0, #MachState__isValid] + + str lr, [r0, #LazyMachState_captureIp] + str sp, [r0, #LazyMachState_captureSp] + + add r1, r0, #LazyMachState_captureR4_R11 + stm r1, {r4-r11} + + mov pc, lr + + LEAF_END LazyMachStateCaptureState, _TEXT + +// void SinglecastDelegateInvokeStub(Delegate *pThis) + LEAF_ENTRY SinglecastDelegateInvokeStub, _TEXT + cmp r0, #0 + beq LOCAL_LABEL(LNullThis) + + ldr r12, [r0, #DelegateObject___methodPtr] + ldr r0, [r0, #DelegateObject___target] + + bx r12 + +LOCAL_LABEL(LNullThis): + mov r0, #CORINFO_NullReferenceException_ASM + b C_FUNC(JIT_InternalThrow) + + LEAF_END SinglecastDelegateInvokeStub, _TEXT + +// +// r12 = UMEntryThunk* +// + NESTED_ENTRY TheUMEntryPrestub,_TEXT,NoHandler + + PROLOG_PUSH "{r0-r4,r7,r8,lr}" // add r8 to make stack aligned by 8B + PROLOG_STACK_SAVE_OFFSET r7, #20 + vpush {d0-d7} + + CHECK_STACK_ALIGNMENT + + mov r0, r12 + bl C_FUNC(TheUMEntryPrestubWorker) + + // Record real target address in r12. + mov r12, r0 + + // Epilog + vpop {d0-d7} + pop {r0-r4,r7,r8,lr} + bx r12 + + NESTED_END TheUMEntryPrestub,_TEXT + +// +// r12 = UMEntryThunk* +// + NESTED_ENTRY UMThunkStub,_TEXT,UnhandledExceptionHandlerUnix + PROLOG_PUSH "{r4,r5,r7,r11,lr}" + PROLOG_STACK_SAVE_OFFSET r7, #8 + + alloc_stack 4 * 5 + stm sp, {r0-r3,r12} + + //GBLA UMThunkStub_HiddenArgOffest // offset of saved UMEntryThunk * + //GBLA UMThunkStub_StackArgsOffest // offset of original stack args + //GBLA UMThunkStub_StackArgsSize // total size of UMThunkStub frame +UMThunkStub_HiddenArgOffset = (-3)*4 +UMThunkStub_StackArgsOffset = 3*4 +UMThunkStub_StackArgsSize = 10*4 + + CHECK_STACK_ALIGNMENT + + bl C_FUNC(GetThread) + cbz r0, LOCAL_LABEL(UMThunkStub_DoThreadSetup) + +LOCAL_LABEL(UMThunkStub_HaveThread): + mov r5, r0 // r5 = Thread * + + ldr r2, =g_TrapReturningThreads + + mov r4, 1 + str r4, [r5, #Thread__m_fPreemptiveGCDisabled] + + ldr r3, [r2] + cbnz r3, LOCAL_LABEL(UMThunkStub_DoTrapReturningThreads) + +LOCAL_LABEL(UMThunkStub_InCooperativeMode): + ldr r12, [r7, #UMThunkStub_HiddenArgOffset] + + ldr r0, [r5, #Thread__m_pDomain] + ldr r1, [r12, #UMEntryThunk__m_dwDomainId] + ldr r0, [r0, #AppDomain__m_dwId] + ldr r3, [r12, #UMEntryThunk__m_pUMThunkMarshInfo] + cmp r0, r1 + bne LOCAL_LABEL(UMThunkStub_WrongAppDomain) + + ldr r2, [r3, #UMThunkMarshInfo__m_cbActualArgSize] + cbz r2, LOCAL_LABEL(UMThunkStub_ArgumentsSetup) + + add r0, r7, #UMThunkStub_StackArgsOffset // Source pointer + add r0, r0, r2 + lsr r1, r2, #2 // Count of stack slots to copy + + and r2, r2, #4 // Align the stack + sub sp, sp, r2 + +LOCAL_LABEL(UMThunkStub_StackLoop): + ldr r2, [r0,#-4]! + str r2, [sp,#-4]! + subs r1, r1, #1 + bne LOCAL_LABEL(UMThunkStub_StackLoop) + +LOCAL_LABEL(UMThunkStub_ArgumentsSetup): + ldr r4, [r3, #UMThunkMarshInfo__m_pILStub] + + // reload argument registers + sub r0, r7, #28 + ldm r0, {r0-r3} + + CHECK_STACK_ALIGNMENT + + blx r4 + +LOCAL_LABEL(UMThunkStub_PostCall): + mov r4, 0 + str r4, [r5, #Thread__m_fPreemptiveGCDisabled] + + EPILOG_STACK_RESTORE_OFFSET r7, #8 + EPILOG_POP "{r4,r5,r7,r11,pc}" + +LOCAL_LABEL(UMThunkStub_DoThreadSetup): + sub sp, #SIZEOF__FloatArgumentRegisters + vstm sp, {d0-d7} + bl C_FUNC(CreateThreadBlockThrow) + vldm sp, {d0-d7} + add sp, #SIZEOF__FloatArgumentRegisters + b LOCAL_LABEL(UMThunkStub_HaveThread) + +LOCAL_LABEL(UMThunkStub_DoTrapReturningThreads): + sub sp, #SIZEOF__FloatArgumentRegisters + vstm sp, {d0-d7} + mov r0, r5 // Thread* pThread + ldr r1, [r7, #UMThunkStub_HiddenArgOffset] // UMEntryThunk* pUMEntry + bl C_FUNC(UMThunkStubRareDisableWorker) + vldm sp, {d0-d7} + add sp, #SIZEOF__FloatArgumentRegisters + b LOCAL_LABEL(UMThunkStub_InCooperativeMode) + +LOCAL_LABEL(UMThunkStub_WrongAppDomain): + sub sp, #SIZEOF__FloatArgumentRegisters + vstm sp, {d0-d7} + + ldr r0, [r7, #UMThunkStub_HiddenArgOffset] // UMEntryThunk* pUMEntry + mov r2, r7 // void * pArgs + // remaining arguments are unused + bl C_FUNC(UM2MDoADCallBack) + + // Restore non-FP return value. + ldr r0, [r7, #0] + ldr r1, [r7, #4] + + // Restore FP return value or HFA. + vldm sp, {d0-d3} + b LOCAL_LABEL(UMThunkStub_PostCall) + + NESTED_END UMThunkStub,_TEXT + +// UM2MThunk_WrapperHelper(void *pThunkArgs, // r0 +// int cbStackArgs, // r1 (unused) +// void *pAddr, // r2 (unused) +// UMEntryThunk *pEntryThunk, // r3 +// Thread *pThread) // [sp, #0] + + NESTED_ENTRY UM2MThunk_WrapperHelper, _TEXT, NoHandler + + PROLOG_PUSH "{r4-r7,r11,lr}" + PROLOG_STACK_SAVE_OFFSET r7, #12 + + CHECK_STACK_ALIGNMENT + + mov r12, r3 // r12 = UMEntryThunk * + + // + // Note that layout of the arguments is given by UMThunkStub frame + // + mov r5, r0 // r5 = pArgs + + ldr r3, [r12, #UMEntryThunk__m_pUMThunkMarshInfo] + + ldr r2, [r3, #UMThunkMarshInfo__m_cbActualArgSize] + cbz r2, LOCAL_LABEL(UM2MThunk_WrapperHelper_ArgumentsSetup) + + add r0, r5, #UMThunkStub_StackArgsSize // Source pointer + add r0, r0, r2 + lsr r1, r2, #2 // Count of stack slots to copy + + and r2, r2, #4 // Align the stack + sub sp, sp, r2 + +LOCAL_LABEL(UM2MThunk_WrapperHelper_StackLoop): + ldr r2, [r0,#-4]! + str r2, [sp,#-4]! + subs r1, r1, #1 + bne LOCAL_LABEL(UM2MThunk_WrapperHelper_StackLoop) + +LOCAL_LABEL(UM2MThunk_WrapperHelper_ArgumentsSetup): + ldr r4, [r3, #UMThunkMarshInfo__m_pILStub] + + // reload floating point registers + sub r6, r5, #SIZEOF__FloatArgumentRegisters + vldm r6, {d0-d7} + + // reload argument registers + ldm r5, {r0-r3} + + CHECK_STACK_ALIGNMENT + + blx r4 + + // Save non-floating point return + str r0, [r5, #0] + str r1, [r5, #4] + + // Save FP return value or HFA. + vstm r6, {d0-d3} + +#ifdef _DEBUG + // trash the floating point registers to ensure that the HFA return values + // won't survive by accident + vldm sp, {d0-d3} +#endif + + EPILOG_STACK_RESTORE_OFFSET r7, #12 + EPILOG_POP "{r4-r7,r11,pc}" + + NESTED_END UM2MThunk_WrapperHelper, _TEXT + +// ------------------------------------------------------------------ + + NESTED_ENTRY ThePreStub, _TEXT, NoHandler + + PROLOG_WITH_TRANSITION_BLOCK + + add r0, sp, #__PWTB_TransitionBlock // pTransitionBlock + mov r1, r12 // pMethodDesc + + bl C_FUNC(PreStubWorker) + + mov r12, r0 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + bx r12 + + NESTED_END ThePreStub, _TEXT + +// ------------------------------------------------------------------ +// This method does nothing. It's just a fixed function for the debugger to put a breakpoint on. + LEAF_ENTRY ThePreStubPatch, _TEXT + nop +ThePreStubPatchLabel: + .global ThePreStubPatchLabel + bx lr + LEAF_END ThePreStubPatch, _TEXT + +// ------------------------------------------------------------------ +// The call in ndirect import precode points to this function. + NESTED_ENTRY NDirectImportThunk, _TEXT, NoHandler + + PROLOG_PUSH "{r0-r4,r7,r8,lr}" // Spill general argument registers, return address and + PROLOG_STACK_SAVE_OFFSET r7, #20 + // arbitrary register to keep stack aligned + vpush {d0-d7} // Spill floating point argument registers + + CHECK_STACK_ALIGNMENT + + mov r0, r12 + bl C_FUNC(NDirectImportWorker) + mov r12, r0 + + vpop {d0-d7} + pop {r0-r4,r7,r8,lr} + + // If we got back from NDirectImportWorker, the MD has been successfully + // linked. Proceed to execute the original DLL call. + bx r12 + + NESTED_END NDirectImportThunk, _TEXT + +// ------------------------------------------------------------------ +// The call in fixup precode initally points to this function. +// The pupose of this function is to load the MethodDesc and forward the call the prestub. + NESTED_ENTRY PrecodeFixupThunk, _TEXT, NoHandler + + // r12 = FixupPrecode * + + PROLOG_PUSH "{r0-r1}" + + // Inline computation done by FixupPrecode::GetMethodDesc() + ldrb r0, [r12, #3] // m_PrecodeChunkIndex + ldrb r1, [r12, #2] // m_MethodDescChunkIndex + + add r12,r12,r0,lsl #3 + add r0,r12,r0,lsl #2 + ldr r0, [r0,#8] + add r12,r0,r1,lsl #2 + + EPILOG_POP "{r0-r1}" + b C_FUNC(ThePreStub) + + NESTED_END PrecodeFixupThunk, _TEXT + +// ------------------------------------------------------------------ +// void ResolveWorkerAsmStub(r0, r1, r2, r3, r4:IndirectionCellAndFlags, r12:DispatchToken) +// +// The stub dispatch thunk which transfers control to VSD_ResolveWorker. + NESTED_ENTRY ResolveWorkerAsmStub, _TEXT, NoHandler + + PROLOG_WITH_TRANSITION_BLOCK + + add r0, sp, #__PWTB_TransitionBlock // pTransitionBlock + mov r2, r12 // token + + // indirection cell in r4 - should be consistent with REG_ARM_STUB_SPECIAL + bic r1, r4, #3 // indirection cell + and r3, r4, #3 // flags + + bl C_FUNC(VSD_ResolveWorker) + + mov r12, r0 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + bx r12 + + NESTED_END ResolveWorkerAsmStub, _TEXT + +// ------------------------------------------------------------------ +// void ResolveWorkerChainLookupAsmStub(r0, r1, r2, r3, r4:IndirectionCellAndFlags, r12:DispatchToken) + NESTED_ENTRY ResolveWorkerChainLookupAsmStub, _TEXT, NoHandler + + // ARMSTUB TODO: implement chained lookup + b C_FUNC(ResolveWorkerAsmStub) + + NESTED_END ResolveWorkerChainLookupAsmStub, _TEXT + + // + // If a preserved register were pushed onto the stack between + // the managed caller and the H_M_F, _R4_R11 will point to its + // location on the stack and it would have been updated on the + // stack by the GC already and it will be popped back into the + // appropriate register when the appropriate epilog is run. + // + // Otherwise, the register is preserved across all the code + // in this HCALL or FCALL, so we need to update those registers + // here because the GC will have updated our copies in the + // frame. + // + // So, if _R4_R11 points into the MachState, we need to update + // the register here. That's what this macro does. + // + + .macro RestoreRegMS regIndex, reg + + // Incoming: + // + // R0 = address of MachState + // + // $regIndex: Index of the register (R4-R11). For R4, index is 4. + // For R5, index is 5, and so on. + // + // $reg: Register name (e.g. R4, R5, etc) + // + // Get the address of the specified captured register from machine state + add r2, r0, #(MachState__captureR4_R11 + ((\regIndex-4)*4)) + + // Get the address of the specified preserved register from machine state + ldr r3, [r0, #(MachState___R4_R11 + ((\regIndex-4)*4))] + + cmp r2, r3 + bne 0f + ldr \reg, [r2] +0: + + .endm + +// +// EXTERN_C void ProfileEnterNaked(FunctionIDOrClientID functionIDOrClientID); +// +NESTED_ENTRY ProfileEnterNaked, _TEXT, NoHandler + PROLOG_PUSH "{r4, r5, r7, r11, lr}" + PROLOG_STACK_SAVE_OFFSET r7, #8 + + // fields of PLATFORM_SPECIFIC_DATA, in reverse order + + // UINT32 r0; // Keep r0 & r1 contiguous to make returning 64-bit results easier + // UINT32 r1; + // void *R11; + // void *Pc; + // union // Float arg registers as 32-bit (s0-s15) and 64-bit (d0-d7) + // { + // UINT32 s[16]; + // UINT64 d[8]; + // }; + // FunctionID functionId; + // void *probeSp; // stack pointer of managed function + // void *profiledSp; // location of arguments on stack + // LPVOID hiddenArg; + // UINT32 flags; + movw r4, #1 + push { /* flags */ r4 } + movw r4, #0 + push { /* hiddenArg */ r4 } + add r5, r11, #8 + push { /* profiledSp */ r5 } + add r5, sp, #32 + push { /* probeSp */ r5 } + push { /* functionId */ r0 } + vpush.64 { d0 - d7 } + push { lr } + push { r11 } + push { /* return value, r4 is NULL */ r4 } + push { /* return value, r4 is NULL */ r4 } + mov r1, sp + bl C_FUNC(ProfileEnter) + EPILOG_STACK_RESTORE_OFFSET r7, #8 + EPILOG_POP "{r4, r5, r7, r11, pc}" +NESTED_END ProfileEnterNaked, _TEXT + +// +// EXTERN_C void ProfileLeaveNaked(FunctionIDOrClientID functionIDOrClientID); +// +NESTED_ENTRY ProfileLeaveNaked, _TEXT, NoHandler + PROLOG_PUSH "{r1, r2, r4, r5, r7, r11, lr}" + PROLOG_STACK_SAVE_OFFSET r7, #16 + + // fields of PLATFORM_SPECIFIC_DATA, in reverse order + + // UINT32 r0; // Keep r0 & r1 contiguous to make returning 64-bit results easier + // UINT32 r1; + // void *R11; + // void *Pc; + // union // Float arg registers as 32-bit (s0-s15) and 64-bit (d0-d7) + // { + // UINT32 s[16]; + // UINT64 d[8]; + // }; + // FunctionID functionId; + // void *probeSp; // stack pointer of managed function + // void *profiledSp; // location of arguments on stack + // LPVOID hiddenArg; + // UINT32 flags; + movw r4, #2 + push { /* flags */ r4 } + movw r4, #0 + push { /* hiddenArg */ r4 } + add r5, r11, #8 + push { /* profiledSp */ r5 } + add r5, sp, #40 + push { /* probeSp */ r5 } + push { /* functionId */ r0 } + vpush.64 { d0 - d7 } + push { lr } + push { r11 } + push { r1 } + push { r2 } + mov r1, sp + bl C_FUNC(ProfileLeave) + EPILOG_STACK_RESTORE_OFFSET r7, #16 + EPILOG_POP "{r1, r2, r4, r5, r7, r11, pc}" +NESTED_END ProfileLeaveNaked, _TEXT + +// EXTERN_C int __fastcall HelperMethodFrameRestoreState( +// INDEBUG_COMMA(HelperMethodFrame *pFrame) +// MachState *pState +// ) + LEAF_ENTRY HelperMethodFrameRestoreState, _TEXT + +#ifdef _DEBUG + mov r0, r1 +#endif + + // If machine state is invalid, then simply exit + ldr r1, [r0, #MachState__isValid] + cmp r1, #0 + beq LOCAL_LABEL(Done) + + RestoreRegMS 4, R4 + RestoreRegMS 5, R5 + RestoreRegMS 6, R6 + RestoreRegMS 7, R7 + RestoreRegMS 8, R8 + RestoreRegMS 9, R9 + RestoreRegMS 10, R10 + RestoreRegMS 11, R11 +LOCAL_LABEL(Done): + // Its imperative that the return value of HelperMethodFrameRestoreState is zero + // as it is used in the state machine to loop until it becomes zero. + // Refer to HELPER_METHOD_FRAME_END macro for details. + mov r0,#0 + bx lr + + LEAF_END HelperMethodFrameRestoreState, _TEXT + +#if 0 +// ------------------------------------------------------------------ +// Macro to generate Redirection Stubs +// +// $reason : reason for redirection +// Eg. GCThreadControl +// NOTE: If you edit this macro, make sure you update GetCONTEXTFromRedirectedStubStackFrame. +// This function is used by both the personality routine and the debugger to retrieve the original CONTEXT. + .macro GenerateRedirectedHandledJITCaseStub reason + + NESTED_ENTRY RedirectedHandledJITCaseFor\reason\()_Stub, _TEXT, NoHandler + + PROLOG_PUSH "{r7,lr}" // return address + PROLOG_STACK_SAVE r7 + alloc_stack 4 // stack slot to save the CONTEXT * + + //REDIRECTSTUB_SP_OFFSET_CONTEXT is defined in asmconstants.h + //If CONTEXT is not saved at 0 offset from SP it must be changed as well. + //ASSERT REDIRECTSTUB_SP_OFFSET_CONTEXT == 0 + + // Runtime check for 8-byte alignment. This check is necessary as this function can be + // entered before complete execution of the prolog of another function. + and r0, r7, #4 + sub sp, sp, r0 + + // stack must be 8 byte aligned + CHECK_STACK_ALIGNMENT + + // + // Save a copy of the redirect CONTEXT*. + // This is needed for the debugger to unwind the stack. + // + bl GetCurrentSavedRedirectContext + str r0, [r7] + + // + // Fetch the interrupted pc and save it as our return address. + // + ldr r1, [r0, #CONTEXT_Pc] + str r1, [r7, #8] + + // + // Call target, which will do whatever we needed to do in the context + // of the target thread, and will RtlRestoreContext when it is done. + // + bl _RedirectedHandledJITCaseFor\reason\()_Stub@Thread@@CAXXZ + + EMIT_BREAKPOINT // Unreachable + +// Put a label here to tell the debugger where the end of this function is. +RedirectedHandledJITCaseFor\reason\()_StubEnd: + .global RedirectedHandledJITCaseFor\reason\()_StubEnd + + NESTED_END RedirectedHandledJITCaseFor\reason\()_Stub, _TEXT + + .endm + +// ------------------------------------------------------------------ +// Redirection Stub for GC in fully interruptible method + GenerateRedirectedHandledJITCaseStub GCThreadControl +// ------------------------------------------------------------------ + GenerateRedirectedHandledJITCaseStub DbgThreadControl +// ------------------------------------------------------------------ + GenerateRedirectedHandledJITCaseStub UserSuspend +// ------------------------------------------------------------------ + GenerateRedirectedHandledJITCaseStub YieldTask + +#ifdef _DEBUG +// ------------------------------------------------------------------ +// Redirection Stub for GC Stress + GenerateRedirectedHandledJITCaseStub GCStress +#endif + +#endif + +// ------------------------------------------------------------------ +// Functions to probe for stack space +// Input reg r4 = amount of stack to probe for +// value of reg r4 is preserved on exit from function +// r12 is trashed +// The below two functions were copied from vctools\crt\crtw32\startup\arm\chkstk.asm + + NESTED_ENTRY checkStack, _TEXT, NoHandler + subs r12,sp,r4 + mrc p15,#0,r4,c13,c0,#2 // get TEB * + ldr r4,[r4,#8] // get Stack limit + bcc LOCAL_LABEL(checkStack_neg) // if r12 is less then 0 set it to 0 +LOCAL_LABEL(checkStack_label1): + cmp r12, r4 + bcc C_FUNC(stackProbe) // must probe to extend guardpage if r12 is beyond stackLimit + sub r4, sp, r12 // restore value of r4 + bx lr +LOCAL_LABEL(checkStack_neg): + mov r12, #0 + b LOCAL_LABEL(checkStack_label1) + NESTED_END checkStack, _TEXT + + NESTED_ENTRY stackProbe, _TEXT, NoHandler + PROLOG_PUSH "{r5,r6}" + mov r6, r12 + bfc r6, #0, #0xc // align down (4K) +LOCAL_LABEL(stackProbe_loop): + sub r4,r4,#0x1000 // dec stack Limit by 4K as page size is 4K + ldr r5,[r4] // try to read ... this should move the guard page + cmp r4,r6 + bne LOCAL_LABEL(stackProbe_loop) + EPILOG_POP "{r5,r6}" + sub r4,sp,r12 + bx lr + NESTED_END stackProbe, _TEXT + +//------------------------------------------------ +// VirtualMethodFixupStub +// +// In NGEN images, virtual slots inherited from cross-module dependencies +// point to a jump thunk that calls into the following function that will +// call into a VM helper. The VM helper is responsible for patching up +// thunk, upon executing the precode, so that all subsequent calls go directly +// to the actual method body. +// +// This is done lazily for performance reasons. +// +// On entry: +// +// R0 = "this" pointer +// R12 = Address of thunk + 4 + + NESTED_ENTRY VirtualMethodFixupStub, _TEXT, NoHandler + + // Save arguments and return address + PROLOG_PUSH "{r0-r3, r7,r8, lr}" // keep increase by 8B for alignment + PROLOG_STACK_SAVE_OFFSET r7, #20 + + // Align stack + alloc_stack SIZEOF__FloatArgumentRegisters + 4 + vstm sp, {d0-d7} + + + CHECK_STACK_ALIGNMENT + + // R12 contains an address that is 4 bytes ahead of + // where the thunk starts. Refer to ZapImportVirtualThunk::Save + // for details on this. + // + // Move the correct thunk start address in R1 + sub r1, r12, #4 + + // Call the helper in the VM to perform the actual fixup + // and tell us where to tail call. R0 already contains + // the this pointer. + bl C_FUNC(VirtualMethodFixupWorker) + + // On return, R0 contains the target to tailcall to + mov r12, r0 + + // pop the stack and restore original register state + vldm sp, {d0-d7} + free_stack SIZEOF__FloatArgumentRegisters + 4 + pop {r0-r3, r7,r8, lr} + + PATCH_LABEL VirtualMethodFixupPatchLabel + + // and tailcall to the actual method + bx r12 + + NESTED_END VirtualMethodFixupStub, _TEXT + +//------------------------------------------------ +// ExternalMethodFixupStub +// +// In NGEN images, calls to cross-module external methods initially +// point to a jump thunk that calls into the following function that will +// call into a VM helper. The VM helper is responsible for patching up the +// thunk, upon executing the precode, so that all subsequent calls go directly +// to the actual method body. +// +// This is done lazily for performance reasons. +// +// On entry: +// +// R12 = Address of thunk + 4 + + NESTED_ENTRY ExternalMethodFixupStub, _TEXT, NoHandler + + PROLOG_WITH_TRANSITION_BLOCK + + add r0, sp, #__PWTB_TransitionBlock // pTransitionBlock + + // Adjust (read comment above for details) and pass the address of the thunk + sub r1, r12, #4 // pThunk + + mov r2, #0 // sectionIndex + mov r3, #0 // pModule + bl C_FUNC(ExternalMethodFixupWorker) + + // mov the address we patched to in R12 so that we can tail call to it + mov r12, r0 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + PATCH_LABEL ExternalMethodFixupPatchLabel + bx r12 + + NESTED_END ExternalMethodFixupStub, _TEXT + +//------------------------------------------------ +// StubDispatchFixupStub +// +// In NGEN images, calls to interface methods initially +// point to a jump thunk that calls into the following function that will +// call into a VM helper. The VM helper is responsible for patching up the +// thunk with actual stub dispatch stub. +// +// On entry: +// +// R4 = Address of indirection cell + + NESTED_ENTRY StubDispatchFixupStub, _TEXT, NoHandler + + PROLOG_WITH_TRANSITION_BLOCK + + // address of StubDispatchFrame + add r0, sp, #__PWTB_TransitionBlock // pTransitionBlock + mov r1, r4 // siteAddrForRegisterIndirect + mov r2, #0 // sectionIndex + mov r3, #0 // pModule + + bl C_FUNC(StubDispatchFixupWorker) + + // mov the address we patched to in R12 so that we can tail call to it + mov r12, r0 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + PATCH_LABEL StubDispatchFixupPatchLabel + bx r12 + + NESTED_END StubDispatchFixupStub, _TEXT + +//------------------------------------------------ +// JIT_RareDisableHelper +// +// The JIT expects this helper to preserve registers used for return values +// + NESTED_ENTRY JIT_RareDisableHelper, _TEXT, NoHandler + + PROLOG_PUSH "{r0-r1, r7,r8, r11, lr}" // save integer return value + PROLOG_STACK_SAVE_OFFSET r7, #8 + vpush {d0-d3} // floating point return value + + CHECK_STACK_ALIGNMENT + + bl C_FUNC(JIT_RareDisableHelperWorker) + + vpop {d0-d3} + EPILOG_POP "{r0-r1, r7,r8, r11, pc}" + + NESTED_END JIT_RareDisableHelper, _TEXT + + +#ifdef FEATURE_CORECLR +// +// JIT Static access helpers for single appdomain case +// + +// ------------------------------------------------------------------ +// void* JIT_GetSharedNonGCStaticBase(SIZE_T moduleDomainID, DWORD dwClassDomainID) + + LEAF_ENTRY JIT_GetSharedNonGCStaticBase_SingleAppDomain, _TEXT + + // If class is not initialized, bail to C++ helper + add r2, r0, #DomainLocalModule__m_pDataBlob + ldrb r2, [r2, r1] + tst r2, #1 + beq LOCAL_LABEL(CallCppHelper1) + + bx lr + +LOCAL_LABEL(CallCppHelper1): + // Tail call JIT_GetSharedNonGCStaticBase_Helper + b C_FUNC(JIT_GetSharedNonGCStaticBase_Helper) + LEAF_END JIT_GetSharedNonGCStaticBase_SingleAppDomain, _TEXT + + +// ------------------------------------------------------------------ +// void* JIT_GetSharedNonGCStaticBaseNoCtor(SIZE_T moduleDomainID, DWORD dwClassDomainID) + + LEAF_ENTRY JIT_GetSharedNonGCStaticBaseNoCtor_SingleAppDomain, _TEXT + + bx lr + LEAF_END JIT_GetSharedNonGCStaticBaseNoCtor_SingleAppDomain, _TEXT + + +// ------------------------------------------------------------------ +// void* JIT_GetSharedGCStaticBase(SIZE_T moduleDomainID, DWORD dwClassDomainID) + + LEAF_ENTRY JIT_GetSharedGCStaticBase_SingleAppDomain, _TEXT + + // If class is not initialized, bail to C++ helper + add r2, r0, #DomainLocalModule__m_pDataBlob + ldrb r2, [r2, r1] + tst r2, #1 + beq LOCAL_LABEL(CallCppHelper3) + + ldr r0, [r0, #DomainLocalModule__m_pGCStatics] + bx lr + +LOCAL_LABEL(CallCppHelper3): + // Tail call Jit_GetSharedGCStaticBase_Helper + b C_FUNC(JIT_GetSharedGCStaticBase_Helper) + LEAF_END JIT_GetSharedGCStaticBase_SingleAppDomain, _TEXT + + +// ------------------------------------------------------------------ +// void* JIT_GetSharedGCStaticBaseNoCtor(SIZE_T moduleDomainID, DWORD dwClassDomainID) + + LEAF_ENTRY JIT_GetSharedGCStaticBaseNoCtor_SingleAppDomain, _TEXT + + ldr r0, [r0, #DomainLocalModule__m_pGCStatics] + bx lr + LEAF_END JIT_GetSharedGCStaticBaseNoCtor_SingleAppDomain, _TEXT + +#endif + +// ------------------------------------------------------------------ +// __declspec(naked) void F_CALL_CONV JIT_Stelem_Ref(PtrArray* array, unsigned idx, Object* val) + LEAF_ENTRY JIT_Stelem_Ref, _TEXT + + // We retain arguments as they were passed and use r0 == array// r1 == idx// r2 == val + + // check for null array + cbz r0, LOCAL_LABEL(ThrowNullReferenceException) + + // idx bounds check + ldr r3,[r0,#ArrayBase__m_NumComponents] + cmp r3,r1 + bls LOCAL_LABEL(ThrowIndexOutOfRangeException) + + // fast path to null assignment (doesn't need any write-barriers) + cbz r2, LOCAL_LABEL(AssigningNull) + + // Verify the array-type and val-type matches before writing + ldr r12, [r0] // r12 = array MT + ldr r3, [r2] // r3 = val->GetMethodTable() + ldr r12, [r12, #MethodTable__m_ElementType] // array->GetArrayElementTypeHandle() + cmp r3, r12 + beq C_FUNC(JIT_Stelem_DoWrite) + + // Types didnt match but allow writing into an array of objects + ldr r3, =g_pObjectClass + ldr r3, [r3] // r3 = *g_pObjectClass + cmp r3, r12 // array type matches with Object* + beq C_FUNC(JIT_Stelem_DoWrite) + + // array type and val type do not exactly match. Raise frame and do detailed match + b C_FUNC(JIT_Stelem_Ref_NotExactMatch) + +LOCAL_LABEL(AssigningNull): + // Assigning null doesn't need write barrier + adds r0, r1, LSL #2 // r0 = r0 + (r1 x 4) = array->m_array[idx] + str r2, [r0, #PtrArray__m_Array] // array->m_array[idx] = val + bx lr + +LOCAL_LABEL(ThrowNullReferenceException): + // Tail call JIT_InternalThrow(NullReferenceException) + ldr r0, =CORINFO_NullReferenceException_ASM + b C_FUNC(JIT_InternalThrow) + +LOCAL_LABEL(ThrowIndexOutOfRangeException): + // Tail call JIT_InternalThrow(NullReferenceException) + ldr r0, =CORINFO_IndexOutOfRangeException_ASM + b C_FUNC(JIT_InternalThrow) + + LEAF_END JIT_Stelem_Ref, _TEXT + +// ------------------------------------------------------------------ +// __declspec(naked) void F_CALL_CONV JIT_Stelem_Ref_NotExactMatch(PtrArray* array, +// unsigned idx, Object* val) +// r12 = array->GetArrayElementTypeHandle() +// + NESTED_ENTRY JIT_Stelem_Ref_NotExactMatch, _TEXT, NoHandler + push {lr} + push {r0-r2} + + CHECK_STACK_ALIGNMENT + + // allow in case val can be casted to array element type + // call ObjIsInstanceOfNoGC(val, array->GetArrayElementTypeHandle()) + mov r1, r12 // array->GetArrayElementTypeHandle() + mov r0, r2 + bl C_FUNC(ObjIsInstanceOfNoGC) + cmp r0, TypeHandle_CanCast + beq LOCAL_LABEL(DoWrite) // ObjIsInstance returned TypeHandle::CanCast + + // check via raising frame +LOCAL_LABEL(NeedFrame): + mov r1, sp // r1 = &array + adds r0, sp, #8 // r0 = &val + bl C_FUNC(ArrayStoreCheck) // ArrayStoreCheck(&val, &array) + +LOCAL_LABEL(DoWrite): + pop {r0-r2} + pop {lr} + b C_FUNC(JIT_Stelem_DoWrite) + + NESTED_END JIT_Stelem_Ref_NotExactMatch, _TEXT + +// ------------------------------------------------------------------ +// __declspec(naked) void F_CALL_CONV JIT_Stelem_DoWrite(PtrArray* array, unsigned idx, Object* val) + LEAF_ENTRY JIT_Stelem_DoWrite, _TEXT + + // Setup args for JIT_WriteBarrier. r0 = &array->m_array[idx]// r1 = val + adds r0, #PtrArray__m_Array // r0 = &array->m_array + adds r0, r1, LSL #2 + mov r1, r2 // r1 = val + + // Branch to the write barrier (which is already correctly overwritten with + // single or multi-proc code based on the current CPU + b C_FUNC(JIT_WriteBarrier) + + LEAF_END JIT_Stelem_DoWrite, _TEXT + +#define __wbScratch r3 +#define pShadow r7 + + .macro START_WRITE_BARRIER name + __\name\()__g_lowest_address_offset = 0xffff + __\name\()__g_highest_address_offset = 0xffff + __\name\()__g_ephemeral_low_offset = 0xffff + __\name\()__g_ephemeral_high_offset = 0xffff + __\name\()__g_card_table_offset = 0xffff + .endm + + .macro LOAD_GC_GLOBAL name, regName, globalName +\name\()__\globalName\()_offset: + __\name\()__\globalName\()_offset = (\name\()__\globalName\()_offset - \name) + movw \regName, #0 + movt \regName, #0 + .endm + + .macro UPDATE_GC_SHADOW name, ptrReg, valReg + // Todo: implement, debugging helper + .endm + + .macro UPDATE_CARD_TABLE name, ptrReg, valReg, mp, postGrow, tmpReg + + LOAD_GC_GLOBAL \name, __wbScratch, g_ephemeral_low + cmp \valReg, __wbScratch + blo 0f + + .if(\postGrow) + LOAD_GC_GLOBAL \name, __wbScratch, g_ephemeral_high + cmp \valReg, __wbScratch + bhs 0f + .endif + + LOAD_GC_GLOBAL \name, __wbScratch, g_card_table + add __wbScratch, __wbScratch, \ptrReg, lsr #10 + + .if(\mp) + ldrb \tmpReg, [__wbScratch] + cmp \tmpReg, #0xff + itt ne + movne \tmpReg, 0xff + strbne \tmpReg, [__wbScratch] + .else + mov \tmpReg, #0xff + strb \tmpReg, [__wbScratch] + .endif + +0: + .endm + + .macro CHECK_GC_HEAP_RANGE name, ptrReg, label + LOAD_GC_GLOBAL \name, __wbScratch, g_lowest_address + cmp \ptrReg, __wbScratch + blo \label + LOAD_GC_GLOBAL \name, __wbScratch, g_highest_address + cmp \ptrReg, __wbScratch + bhs \label + .endm + + .macro JIT_WRITEBARRIER name, mp, post + LEAF_ENTRY \name, _TEXT + START_WRITE_BARRIER \name + .if(\mp) + dmb + .endif + + str r1, [r0] + UPDATE_GC_SHADOW \name, r0, r1 + UPDATE_CARD_TABLE \name, r0, r1, \mp, \post, r0 + bx lr + LEAF_END_MARKED \name, _TEXT + .endm + + .macro JIT_CHECKEDWRITEBARRIER_SP name, post + LEAF_ENTRY \name, _TEXT + START_WRITE_BARRIER \name + str r1, [r0] + CHECK_GC_HEAP_RANGE \name, r0, 1f + UPDATE_GC_SHADOW \name, r0, r1 + UPDATE_CARD_TABLE \name, r0, r1, 0, \post, r0 +1: + bx lr + LEAF_END_MARKED \name, _TEXT + .endm + + .macro JIT_CHECKEDWRITEBARRIER_MP name, post + LEAF_ENTRY \name, _TEXT + START_WRITE_BARRIER \name + dmb + str r1, [r0] + CHECK_GC_HEAP_RANGE \name, r0, 1f + UPDATE_GC_SHADOW \name, r0, r1 + UPDATE_CARD_TABLE \name, r0, r1, 1, \post, r0 + bx lr +1: + str r1, [r0] + bx lr + LEAF_END_MARKED \name, _TEXT + .endm + + .macro JIT_BYREFWRITEBARRIER name, mp, post + LEAF_ENTRY \name, _TEXT + START_WRITE_BARRIER \name + .if(\mp) + dmb + .endif + + ldr r2, [r1] + str r2, [r0] + CHECK_GC_HEAP_RANGE \name, r0, 1f + UPDATE_GC_SHADOW \name, r0, r2 + UPDATE_CARD_TABLE \name, r0, r2, \mp, \post, r2 +1: + add r0, #4 + add r1, #4 + bx lr + LEAF_END_MARKED \name, _TEXT + .endm + + .macro JIT_WRITEBARRIER_DESCRIPTOR name + .word \name + .word \name\()_End + .word __\name\()__g_lowest_address_offset + .word __\name\()__g_highest_address_offset + .word __\name\()__g_ephemeral_low_offset + .word __\name\()__g_ephemeral_high_offset + .word __\name\()__g_card_table_offset + .endm + + // There 4 versions of each write barriers. A 2x2 combination of multi-proc/single-proc and pre/post grow version + JIT_WRITEBARRIER JIT_WriteBarrier_SP_Pre, 0, 0 + JIT_WRITEBARRIER JIT_WriteBarrier_SP_Post, 0, 1 + JIT_WRITEBARRIER JIT_WriteBarrier_MP_Pre, 1, 0 + JIT_WRITEBARRIER JIT_WriteBarrier_MP_Post, 1, 1 + + JIT_CHECKEDWRITEBARRIER_SP JIT_CheckedWriteBarrier_SP_Pre, 0 + JIT_CHECKEDWRITEBARRIER_SP JIT_CheckedWriteBarrier_SP_Post, 1 + JIT_CHECKEDWRITEBARRIER_MP JIT_CheckedWriteBarrier_MP_Pre, 0 + JIT_CHECKEDWRITEBARRIER_MP JIT_CheckedWriteBarrier_MP_Post, 1 + + JIT_BYREFWRITEBARRIER JIT_ByRefWriteBarrier_SP_Pre, 0, 0 + JIT_BYREFWRITEBARRIER JIT_ByRefWriteBarrier_SP_Post, 0, 1 + JIT_BYREFWRITEBARRIER JIT_ByRefWriteBarrier_MP_Pre, 1, 0 + JIT_BYREFWRITEBARRIER JIT_ByRefWriteBarrier_MP_Post, 1, 1 + +// .section .clrwb, "d" +g_rgWriteBarrierDescriptors: + + JIT_WRITEBARRIER_DESCRIPTOR JIT_WriteBarrier_SP_Pre + JIT_WRITEBARRIER_DESCRIPTOR JIT_WriteBarrier_SP_Post + JIT_WRITEBARRIER_DESCRIPTOR JIT_WriteBarrier_MP_Pre + JIT_WRITEBARRIER_DESCRIPTOR JIT_WriteBarrier_MP_Post + + JIT_WRITEBARRIER_DESCRIPTOR JIT_CheckedWriteBarrier_SP_Pre + JIT_WRITEBARRIER_DESCRIPTOR JIT_CheckedWriteBarrier_SP_Post + JIT_WRITEBARRIER_DESCRIPTOR JIT_CheckedWriteBarrier_MP_Pre + JIT_WRITEBARRIER_DESCRIPTOR JIT_CheckedWriteBarrier_MP_Post + + JIT_WRITEBARRIER_DESCRIPTOR JIT_ByRefWriteBarrier_SP_Pre + JIT_WRITEBARRIER_DESCRIPTOR JIT_ByRefWriteBarrier_SP_Post + JIT_WRITEBARRIER_DESCRIPTOR JIT_ByRefWriteBarrier_MP_Pre + JIT_WRITEBARRIER_DESCRIPTOR JIT_ByRefWriteBarrier_MP_Post + + // Sentinel value + .word 0 + +// .text + + .global g_rgWriteBarrierDescriptors + +#ifdef FEATURE_READYTORUN + + NESTED_ENTRY DelayLoad_MethodCall_FakeProlog, _TEXT, NoHandler + + // Match what the lazy thunk has pushed. The actual method arguments will be spilled later. + push {r1-r3} + + // This is where execution really starts. +DelayLoad_MethodCall: + .global DelayLoad_MethodCall + + push {r0} + + PROLOG_WITH_TRANSITION_BLOCK 0x0, 1, DoNotPushArgRegs + + // Load the helper arguments + ldr r5, [sp,#(__PWTB_TransitionBlock+10*4)] // pModule + ldr r6, [sp,#(__PWTB_TransitionBlock+11*4)] // sectionIndex + ldr r7, [sp,#(__PWTB_TransitionBlock+12*4)] // indirection + + // Spill the actual method arguments + str r1, [sp,#(__PWTB_TransitionBlock+10*4)] + str r2, [sp,#(__PWTB_TransitionBlock+11*4)] + str r3, [sp,#(__PWTB_TransitionBlock+12*4)] + + add r0, sp, #__PWTB_TransitionBlock // pTransitionBlock + + mov r1, r7 // pIndirection + mov r2, r6 // sectionIndex + mov r3, r5 // pModule + + bl C_FUNC(ExternalMethodFixupWorker) + + // mov the address we patched to in R12 so that we can tail call to it + mov r12, r0 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + + // Share the patch label + b C_FUNC(ExternalMethodFixupPatchLabel) + + NESTED_END DelayLoad_MethodCall_FakeProlog, _TEXT + + + .macro DynamicHelper frameFlags, suffix + +__FakePrologName="DelayLoad_Helper\suffix\()_FakeProlog" + + NESTED_ENTRY DelayLoad_Helper\suffix\()_FakeProlog, _TEXT, NoHandler + + // Match what the lazy thunk has pushed. The actual method arguments will be spilled later. + push {r1-r3} + + // This is where execution really starts. +DelayLoad_Helper\suffix: + .global DelayLoad_Helper\suffix + + push {r0} + + PROLOG_WITH_TRANSITION_BLOCK 0x4, 0, DoNotPushArgRegs + + // Load the helper arguments + ldr r5, [sp,#(__PWTB_TransitionBlock+10*4)] // pModule + ldr r6, [sp,#(__PWTB_TransitionBlock+11*4)] // sectionIndex + ldr r7, [sp,#(__PWTB_TransitionBlock+12*4)] // indirection + + // Spill the actual method arguments + str r1, [sp,#(__PWTB_TransitionBlock+10*4)] + str r2, [sp,#(__PWTB_TransitionBlock+11*4)] + str r3, [sp,#(__PWTB_TransitionBlock+12*4)] + + add r0, sp, #__PWTB_TransitionBlock // pTransitionBlock + + mov r1, r7 // pIndirection + mov r2, r6 // sectionIndex + mov r3, r5 // pModule + + mov r4, \frameFlags + str r4, [sp,#0] + + bl C_FUNC(DynamicHelperWorker) + + cbnz r0, 0f + ldr r0, [sp,#(__PWTB_TransitionBlock+9*4)] // The result is stored in the argument area of the transition block + + EPILOG_WITH_TRANSITION_BLOCK_RETURN + +0: + mov r12, r0 + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + bx r12 + + NESTED_END DelayLoad_Helper\suffix\()_FakeProlog, _TEXT + + .endm + + DynamicHelper DynamicHelperFrameFlags_Default + DynamicHelper DynamicHelperFrameFlags_ObjectArg, _Obj + DynamicHelper DynamicHelperFrameFlags_ObjectArg | DynamicHelperFrameFlags_ObjectArg2, _ObjObj + +#endif // FEATURE_READYTORUN + +#ifdef FEATURE_HIJACK + +// ------------------------------------------------------------------ +// Hijack function for functions which return a value type + NESTED_ENTRY OnHijackTripThread, _TEXT, NoHandler + PROLOG_PUSH "{r0,r4-r11,lr}" + + PROLOG_VPUSH "{d0-d3}" // saving as d0-d3 can have the floating point return value + PROLOG_PUSH "{r1}" // saving as r1 can have partial return value when return is > 32 bits + alloc_stack 4 // 8 byte align + + CHECK_STACK_ALIGNMENT + + add r0, sp, #40 + bl C_FUNC(OnHijackWorker) + + free_stack 4 + EPILOG_POP "{r1}" + EPILOG_VPOP "{d0-d3}" + + EPILOG_POP "{r0,r4-r11,pc}" + NESTED_END OnHijackTripThread, _TEXT +#endif + diff --git a/src/vm/arm/asmhelpers.asm b/src/vm/arm/asmhelpers.asm new file mode 100644 index 0000000000..796c1d14c5 --- /dev/null +++ b/src/vm/arm/asmhelpers.asm @@ -0,0 +1,2727 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +;; ==++== +;; + +;; +;; ==--== +#include "ksarm.h" + +#include "asmconstants.h" + +#include "asmmacros.h" + + SETALIAS CTPMethodTable__s_pThunkTable, ?s_pThunkTable@CTPMethodTable@@0PAVMethodTable@@A + SETALIAS g_pObjectClass, ?g_pObjectClass@@3PAVMethodTable@@A + + IMPORT GetThread + IMPORT JIT_InternalThrow + IMPORT JIT_WriteBarrier + IMPORT TheUMEntryPrestubWorker + IMPORT CreateThreadBlockThrow + IMPORT UMThunkStubRareDisableWorker + IMPORT UM2MDoADCallBack + IMPORT PreStubWorker + IMPORT NDirectImportWorker + IMPORT ObjIsInstanceOfNoGC + IMPORT ArrayStoreCheck + IMPORT VSD_ResolveWorker + IMPORT $g_pObjectClass + +#ifdef WRITE_BARRIER_CHECK + SETALIAS g_GCShadow, ?g_GCShadow@@3PAEA + SETALIAS g_GCShadowEnd, ?g_GCShadowEnd@@3PAEA + + IMPORT g_lowest_address + IMPORT $g_GCShadow + IMPORT $g_GCShadowEnd +#endif // WRITE_BARRIER_CHECK + + +#ifdef FEATURE_REMOTING + IMPORT $CTPMethodTable__s_pThunkTable + IMPORT VSD_GetTargetForTPWorker + IMPORT VSD_GetTargetForTPWorkerQuick + IMPORT TransparentProxyStubWorker +#endif +#ifdef FEATURE_COMINTEROP + IMPORT CLRToCOMWorker + IMPORT ComPreStubWorker + IMPORT COMToCLRWorker +#endif + IMPORT CallDescrWorkerUnwindFrameChainHandler + IMPORT UMEntryPrestubUnwindFrameChainHandler + IMPORT UMThunkStubUnwindFrameChainHandler +#ifdef FEATURE_COMINTEROP + IMPORT ReverseComUnwindFrameChainHandler +#endif + +#ifdef FEATURE_HIJACK + IMPORT OnHijackWorker +#endif ;FEATURE_HIJACK + + IMPORT GetCurrentSavedRedirectContext + +#ifdef FEATURE_MIXEDMODE + SETALIAS IJWNOADThunk__FindThunkTarget, ?FindThunkTarget@IJWNOADThunk@@QAAPBXXZ + IMPORT $IJWNOADThunk__FindThunkTarget +#endif + + ;; Imports to support virtual import fixup for ngen images + IMPORT VirtualMethodFixupWorker + ;; Import to support cross-moodule external method invocation in ngen images + IMPORT ExternalMethodFixupWorker + IMPORT StubDispatchFixupWorker + +#ifdef FEATURE_READYTORUN + IMPORT DynamicHelperWorker +#endif + + IMPORT JIT_RareDisableHelperWorker + IMPORT DoJITFailFast + IMPORT s_gsCookie + IMPORT g_TrapReturningThreads + + ;; Imports for singleDomain statics helpers + IMPORT JIT_GetSharedNonGCStaticBase_Helper + IMPORT JIT_GetSharedGCStaticBase_Helper + + TEXTAREA + +;; LPVOID __stdcall GetCurrentIP(void); + LEAF_ENTRY GetCurrentIP + mov r0, lr + bx lr + LEAF_END + +;; LPVOID __stdcall GetCurrentSP(void); + LEAF_ENTRY GetCurrentSP + mov r0, sp + bx lr + LEAF_END + +;;----------------------------------------------------------------------------- +;; This helper routine enregisters the appropriate arguments and makes the +;; actual call. +;;----------------------------------------------------------------------------- +;;void CallDescrWorkerInternal(CallDescrData * pCallDescrData); + NESTED_ENTRY CallDescrWorkerInternal,,CallDescrWorkerUnwindFrameChainHandler + PROLOG_PUSH {r4,r5,r7,lr} + PROLOG_STACK_SAVE r7 + + mov r5,r0 ; save pCallDescrData in r5 + + ldr r1, [r5,#CallDescrData__numStackSlots] + cbz r1, Ldonestack + + ;; Add frame padding to ensure frame size is a multiple of 8 (a requirement of the OS ABI). + ;; We push four registers (above) and numStackSlots arguments (below). If this comes to an odd number + ;; of slots we must pad with another. This simplifies to "if the low bit of numStackSlots is set, + ;; extend the stack another four bytes". + lsls r2, r1, #2 + and r3, r2, #4 + sub sp, sp, r3 + + ;; This loop copies numStackSlots words + ;; from [pSrcEnd-4,pSrcEnd-8,...] to [sp-4,sp-8,...] + ldr r0, [r5,#CallDescrData__pSrc] + add r0,r0,r2 +Lstackloop + ldr r2, [r0,#-4]! + str r2, [sp,#-4]! + subs r1, r1, #1 + bne Lstackloop +Ldonestack + + ;; If FP arguments are supplied in registers (r3 != NULL) then initialize all of them from the pointer + ;; given in r3. Do not use "it" since it faults in floating point even when the instruction is not executed. + ldr r3, [r5,#CallDescrData__pFloatArgumentRegisters] + cbz r3, LNoFloatingPoint + vldm r3, {s0-s15} +LNoFloatingPoint + + ;; Copy [pArgumentRegisters, ..., pArgumentRegisters + 12] + ;; into r0, ..., r3 + + ldr r4, [r5,#CallDescrData__pArgumentRegisters] + ldm r4, {r0-r3} + + CHECK_STACK_ALIGNMENT + + ;; call pTarget + ;; Note that remoting expect target in r4. + ldr r4, [r5,#CallDescrData__pTarget] + blx r4 + + ldr r3, [r5,#CallDescrData__fpReturnSize] + + ;; Save FP return value if appropriate + cbz r3, LFloatingPointReturnDone + + ;; Float return case + ;; Do not use "it" since it faults in floating point even when the instruction is not executed. + cmp r3, #4 + bne LNoFloatReturn + vmov r0, s0 + b LFloatingPointReturnDone +LNoFloatReturn + + ;; Double return case + ;; Do not use "it" since it faults in floating point even when the instruction is not executed. + cmp r3, #8 + bne LNoDoubleReturn + vmov r0, r1, s0, s1 + b LFloatingPointReturnDone +LNoDoubleReturn + + add r2, r5, #CallDescrData__returnValue + + cmp r3, #16 + bne LNoFloatHFAReturn + vstm r2, {s0-s3} + b LReturnDone +LNoFloatHFAReturn + + cmp r3, #32 + bne LNoDoubleHFAReturn + vstm r2, {d0-d3} + b LReturnDone +LNoDoubleHFAReturn + + EMIT_BREAKPOINT ; Unreachable + +LFloatingPointReturnDone + + ;; Save return value into retbuf + str r0, [r5, #(CallDescrData__returnValue + 0)] + str r1, [r5, #(CallDescrData__returnValue + 4)] + +LReturnDone + +#ifdef _DEBUG + ;; trash the floating point registers to ensure that the HFA return values + ;; won't survive by accident + vldm sp, {d0-d3} +#endif + + EPILOG_STACK_RESTORE r7 + EPILOG_POP {r4,r5,r7,pc} + + NESTED_END + + +;;----------------------------------------------------------------------------- +;; This helper routine is where returns for irregular tail calls end up +:: so they can dynamically pop their stack arguments. +;;----------------------------------------------------------------------------- +; +; Stack Layout (stack grows up, 0 at the top, offsets relative to frame pointer, r7): +; +; sp -> callee stack arguments +; : +; : +; -0Ch gsCookie +; TailCallHelperFrame -> +; -08h __VFN_table +; -04h m_Next +; r7 -> +; +00h m_calleeSavedRgisters.r4 +; +04h .r5 +; +08h .r6 +; +0Ch .r7 +; +10h .r8 +; +14h .r9 +; +18h .r10 +; r11-> +; +1Ch .r11 +; +20h .r14 -or- m_ReturnAddress +; +; r6 -> GetThread() +; r5 -> r6->m_pFrame (old Frame chain head) +; r11 is used to preserve the ETW call stack + + NESTED_ENTRY TailCallHelperStub + ; + ; This prolog is never executed, but we keep it here for reference + ; and for the unwind data it generates + ; + + ; Spill callee saved registers and return address. + PROLOG_PUSH {r4-r11,lr} + + PROLOG_STACK_SAVE r7 + + ; + ; This is the code that would have to run to setup this frame + ; like the C++ helper does before calling RtlRestoreContext + ; + ; Allocate space for the rest of the frame and GSCookie. + ; PROLOG_STACK_ALLOC 0x0C + ; + ; Set r11 for frame chain + ;add r11, r7, 0x1C + ; + ; Set the vtable for TailCallFrame + ;bl TCF_GETMETHODFRAMEVPTR + ;str r0, [r7, #-8] + ; + ; Initialize the GSCookie within the Frame + ;ldr r0, =s_gsCookie + ;str r0, [r7, #-0x0C] + ; + ; Link the TailCallFrameinto the Frame chain + ; and initialize r5 & r6 for unlinking later + ;CALL_GETTHREAD + ;mov r6, r0 + ;ldr r5, [r6, #Thread__m_pFrame] + ;str r5, [r7, #-4] + ;sub r0, r7, 8 + ;str r0, [r6, #Thread__m_pFrame] + ; + ; None of the previous stuff is ever executed, + ; but we keep it here for reference + ; + + ; + ; Here's the pretend call (make it real so the unwinder + ; doesn't think we're in the prolog) + ; + bl TailCallHelperStub + ; + ; with the real return address pointing to this real epilog + ; +JIT_TailCallHelperStub_ReturnAddress + EXPORT JIT_TailCallHelperStub_ReturnAddress + + ; + ; Our epilog (which also unlinks the StubHelperFrame) + ; Be careful not to trash the return registers + ; + +#ifdef _DEBUG + ldr r3, =s_gsCookie + ldr r3, [r3] + ldr r2, [r7, #-0x0C] + cmp r2, r3 + beq GoodGSCookie + bl DoJITFailFast +GoodGSCookie +#endif ; _DEBUG + + ; + ; unlink the TailCallFrame + ; + str r5, [r6, #Thread__m_pFrame] + + ; + ; epilog + ; + EPILOG_STACK_RESTORE r7 + EPILOG_POP {r4-r11,lr} + EPILOG_RETURN + + NESTED_END + +; ------------------------------------------------------------------ + +; void LazyMachStateCaptureState(struct LazyMachState *pState); + LEAF_ENTRY LazyMachStateCaptureState + + ;; marks that this is not yet valid + mov r1, #0 + str r1, [r0, #MachState__isValid] + + str lr, [r0, #LazyMachState_captureIp] + str sp, [r0, #LazyMachState_captureSp] + + add r1, r0, #LazyMachState_captureR4_R11 + stm r1, {r4-r11} + + mov pc, lr + + LEAF_END + +; void SinglecastDelegateInvokeStub(Delegate *pThis) + LEAF_ENTRY SinglecastDelegateInvokeStub + cmp r0, #0 + beq LNullThis + + ldr r12, [r0, #DelegateObject___methodPtr] + ldr r0, [r0, #DelegateObject___target] + + bx r12 + +LNullThis + mov r0, #CORINFO_NullReferenceException_ASM + b JIT_InternalThrow + + LEAF_END + +; +; r12 = UMEntryThunk* +; + NESTED_ENTRY TheUMEntryPrestub,,UMEntryPrestubUnwindFrameChainHandler + + PROLOG_PUSH {r0-r4,lr} + PROLOG_VPUSH {d0-d7} + + CHECK_STACK_ALIGNMENT + + mov r0, r12 + bl TheUMEntryPrestubWorker + + ; Record real target address in r12. + mov r12, r0 + + ; Epilog + EPILOG_VPOP {d0-d7} + EPILOG_POP {r0-r4,lr} + EPILOG_BRANCH_REG r12 + + NESTED_END + +; +; r12 = UMEntryThunk* +; + NESTED_ENTRY UMThunkStub,,UMThunkStubUnwindFrameChainHandler + PROLOG_PUSH {r4,r5,r7,r11,lr} + PROLOG_PUSH {r0-r3,r12} + PROLOG_STACK_SAVE r7 + + GBLA UMThunkStub_HiddenArg ; offset of saved UMEntryThunk * + GBLA UMThunkStub_StackArgs ; offset of original stack args (total size of UMThunkStub frame) +UMThunkStub_HiddenArg SETA 4*4 +UMThunkStub_StackArgs SETA 10*4 + + CHECK_STACK_ALIGNMENT + + bl GetThread + cbz r0, UMThunkStub_DoThreadSetup + +UMThunkStub_HaveThread + mov r5, r0 ; r5 = Thread * + + ldr r2, =g_TrapReturningThreads + + mov r4, 1 + str r4, [r5, #Thread__m_fPreemptiveGCDisabled] + + ldr r3, [r2] + cbnz r3, UMThunkStub_DoTrapReturningThreads + +UMThunkStub_InCooperativeMode + ldr r12, [r7, #UMThunkStub_HiddenArg] + + ldr r0, [r5, #Thread__m_pDomain] + ldr r1, [r12, #UMEntryThunk__m_dwDomainId] + ldr r0, [r0, #AppDomain__m_dwId] + ldr r3, [r12, #UMEntryThunk__m_pUMThunkMarshInfo] + cmp r0, r1 + bne UMThunkStub_WrongAppDomain + + ldr r2, [r3, #UMThunkMarshInfo__m_cbActualArgSize] + cbz r2, UMThunkStub_ArgumentsSetup + + add r0, r7, #UMThunkStub_StackArgs ; Source pointer + add r0, r0, r2 + lsr r1, r2, #2 ; Count of stack slots to copy + + and r2, r2, #4 ; Align the stack + sub sp, sp, r2 + +UMThunkStub_StackLoop + ldr r2, [r0,#-4]! + str r2, [sp,#-4]! + subs r1, r1, #1 + bne UMThunkStub_StackLoop + +UMThunkStub_ArgumentsSetup + ldr r4, [r3, #UMThunkMarshInfo__m_pILStub] + + ; reload argument registers + ldm r7, {r0-r3} + + CHECK_STACK_ALIGNMENT + + blx r4 + +UMThunkStub_PostCall + mov r4, 0 + str r4, [r5, #Thread__m_fPreemptiveGCDisabled] + + EPILOG_STACK_RESTORE r7 + EPILOG_STACK_FREE 4 * 5 + EPILOG_POP {r4,r5,r7,r11,pc} + +UMThunkStub_DoThreadSetup + sub sp, #SIZEOF__FloatArgumentRegisters + vstm sp, {d0-d7} + bl CreateThreadBlockThrow + vldm sp, {d0-d7} + add sp, #SIZEOF__FloatArgumentRegisters + b UMThunkStub_HaveThread + +UMThunkStub_DoTrapReturningThreads + sub sp, #SIZEOF__FloatArgumentRegisters + vstm sp, {d0-d7} + mov r0, r5 ; Thread* pThread + ldr r1, [r7, #UMThunkStub_HiddenArg] ; UMEntryThunk* pUMEntry + bl UMThunkStubRareDisableWorker + vldm sp, {d0-d7} + add sp, #SIZEOF__FloatArgumentRegisters + b UMThunkStub_InCooperativeMode + +UMThunkStub_WrongAppDomain + sub sp, #SIZEOF__FloatArgumentRegisters + vstm sp, {d0-d7} + + ldr r0, [r7, #UMThunkStub_HiddenArg] ; UMEntryThunk* pUMEntry + mov r2, r7 ; void * pArgs + ; remaining arguments are unused + bl UM2MDoADCallBack + + ; Restore non-FP return value. + ldr r0, [r7, #0] + ldr r1, [r7, #4] + + ; Restore FP return value or HFA. + vldm sp, {d0-d3} + b UMThunkStub_PostCall + + NESTED_END + +; UM2MThunk_WrapperHelper(void *pThunkArgs, // r0 +; int cbStackArgs, // r1 (unused) +; void *pAddr, // r2 (unused) +; UMEntryThunk *pEntryThunk, // r3 +; Thread *pThread) // [sp, #0] + + NESTED_ENTRY UM2MThunk_WrapperHelper + + PROLOG_PUSH {r4-r7,r11,lr} + PROLOG_STACK_SAVE r7 + + CHECK_STACK_ALIGNMENT + + mov r12, r3 // r12 = UMEntryThunk * + + ; + ; Note that layout of the arguments is given by UMThunkStub frame + ; + mov r5, r0 // r5 = pArgs + + ldr r3, [r12, #UMEntryThunk__m_pUMThunkMarshInfo] + + ldr r2, [r3, #UMThunkMarshInfo__m_cbActualArgSize] + cbz r2, UM2MThunk_WrapperHelper_ArgumentsSetup + + add r0, r5, #UMThunkStub_StackArgs ; Source pointer + add r0, r0, r2 + lsr r1, r2, #2 ; Count of stack slots to copy + + and r2, r2, #4 ; Align the stack + sub sp, sp, r2 + +UM2MThunk_WrapperHelper_StackLoop + ldr r2, [r0,#-4]! + str r2, [sp,#-4]! + subs r1, r1, #1 + bne UM2MThunk_WrapperHelper_StackLoop + +UM2MThunk_WrapperHelper_ArgumentsSetup + ldr r4, [r3, #UMThunkMarshInfo__m_pILStub] + + ; reload floating point registers + sub r6, r5, #SIZEOF__FloatArgumentRegisters + vldm r6, {d0-d7} + + ; reload argument registers + ldm r5, {r0-r3} + + CHECK_STACK_ALIGNMENT + + blx r4 + + ; Save non-floating point return + str r0, [r5, #0] + str r1, [r5, #4] + + ; Save FP return value or HFA. + vstm r6, {d0-d3} + +#ifdef _DEBUG + ;; trash the floating point registers to ensure that the HFA return values + ;; won't survive by accident + vldm sp, {d0-d3} +#endif + + EPILOG_STACK_RESTORE r7 + EPILOG_POP {r4-r7,r11,pc} + + NESTED_END + +; ------------------------------------------------------------------ +; +; IJWNOADThunk::MakeCall +; +; On entry: +; r12 : IJWNOADThunk * +; +; On exit: +; Tail calls to real managed target +; + +#ifdef FEATURE_MIXEDMODE + NESTED_ENTRY IJWNOADThunk__MakeCall + + ; Can't pass C++ mangled names to NESTED_ENTRY and my attempts to use EQU to define an alternate name + ; for a symbol didn't work. Just define a label for the decorated name of the method and export it + ; manually. +|?MakeCall@IJWNOADThunk@@KAXXZ| + EXPORT |?MakeCall@IJWNOADThunk@@KAXXZ| + + PROLOG_PUSH {r0-r4,lr} + PROLOG_VPUSH {d0-d7} + + CHECK_STACK_ALIGNMENT + + mov r0, r12 ; IJWNOADThunk * is this pointer for IJWNOADThunk::FindThunkTarget + bl $IJWNOADThunk__FindThunkTarget + mov r12, r0 ; Returns real jump target in r0, save this in r12 + + EPILOG_VPOP {d0-d7} + EPILOG_POP {r0-r4,lr} + EPILOG_BRANCH_REG r12 + + NESTED_END +#endif + +; ------------------------------------------------------------------ + + NESTED_ENTRY ThePreStub + + PROLOG_WITH_TRANSITION_BLOCK + + add r0, sp, #__PWTB_TransitionBlock ; pTransitionBlock + mov r1, r12 ; pMethodDesc + + bl PreStubWorker + + mov r12, r0 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + EPILOG_BRANCH_REG r12 + + NESTED_END + +; ------------------------------------------------------------------ +; This method does nothing. It's just a fixed function for the debugger to put a breakpoint on. + LEAF_ENTRY ThePreStubPatch + nop +ThePreStubPatchLabel + EXPORT ThePreStubPatchLabel + bx lr + LEAF_END + +; ------------------------------------------------------------------ +; The call in ndirect import precode points to this function. + NESTED_ENTRY NDirectImportThunk + + PROLOG_PUSH {r0-r4,lr} ; Spill general argument registers, return address and + ; arbitrary register to keep stack aligned + PROLOG_VPUSH {d0-d7} ; Spill floating point argument registers + + CHECK_STACK_ALIGNMENT + + mov r0, r12 + bl NDirectImportWorker + mov r12, r0 + + EPILOG_VPOP {d0-d7} + EPILOG_POP {r0-r4,lr} + + ; If we got back from NDirectImportWorker, the MD has been successfully + ; linked. Proceed to execute the original DLL call. + EPILOG_BRANCH_REG r12 + + NESTED_END + +; ------------------------------------------------------------------ +; The call in fixup precode initally points to this function. +; The pupose of this function is to load the MethodDesc and forward the call the prestub. + NESTED_ENTRY PrecodeFixupThunk + + ; r12 = FixupPrecode * + + PROLOG_PUSH {r0-r1} + + ; Inline computation done by FixupPrecode::GetMethodDesc() + ldrb r0, [r12, #3] ; m_PrecodeChunkIndex + ldrb r1, [r12, #2] ; m_MethodDescChunkIndex + + add r12,r12,r0,lsl #3 + add r0,r12,r0,lsl #2 + ldr r0, [r0,#8] + add r12,r0,r1,lsl #2 + + EPILOG_POP {r0-r1} + EPILOG_BRANCH ThePreStub + + NESTED_END + +; ------------------------------------------------------------------ +; void ResolveWorkerAsmStub(r0, r1, r2, r3, r4:IndirectionCellAndFlags, r12:DispatchToken) +; +; The stub dispatch thunk which transfers control to VSD_ResolveWorker. + NESTED_ENTRY ResolveWorkerAsmStub + + PROLOG_WITH_TRANSITION_BLOCK + + add r0, sp, #__PWTB_TransitionBlock ; pTransitionBlock + mov r2, r12 ; token + + ; indirection cell in r4 - should be consistent with REG_ARM_STUB_SPECIAL + bic r1, r4, #3 ; indirection cell + and r3, r4, #3 ; flags + + bl VSD_ResolveWorker + + mov r12, r0 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + EPILOG_BRANCH_REG r12 + + NESTED_END + +; ------------------------------------------------------------------ +; void ResolveWorkerChainLookupAsmStub(r0, r1, r2, r3, r4:IndirectionCellAndFlags, r12:DispatchToken) + NESTED_ENTRY ResolveWorkerChainLookupAsmStub + + ; ARMSTUB TODO: implement chained lookup + b ResolveWorkerAsmStub + + NESTED_END + +#if defined(FEATURE_REMOTING) || defined(FEATURE_COMINTEROP) + +; ------------------------------------------------------------------ +; setStubReturnValue +; r0 - size of floating point return value (MetaSig::GetFPReturnSize()) +; r1 - pointer to the return buffer in the stub frame + LEAF_ENTRY setStubReturnValue + + cbz r0, NoFloatingPointRetVal + + ;; Float return case + ;; Do not use "it" since it faults in floating point even when the instruction is not executed. + cmp r0, #4 + bne LNoFloatRetVal + vldr s0, [r1] + bx lr +LNoFloatRetVal + + ;; Double return case + ;; Do not use "it" since it faults in floating point even when the instruction is not executed. + cmp r0, #8 + bne LNoDoubleRetVal + vldr d0, [r1] + bx lr +LNoDoubleRetVal + + cmp r0, #16 + bne LNoFloatHFARetVal + vldm r1, {s0-s3} + bx lr +LNoFloatHFARetVal + + cmp r0, #32 + bne LNoDoubleHFARetVal + vldm r1, {d0-d3} + bx lr +LNoDoubleHFARetVal + + EMIT_BREAKPOINT ; Unreachable + +NoFloatingPointRetVal + + ;; Restore the return value from retbuf + ldr r0, [r1] + ldr r1, [r1, #4] + bx lr + + LEAF_END + +#endif // FEATURE_REMOTING || FEATURE_COMINTEROP + +#ifdef FEATURE_REMOTING + +; ------------------------------------------------------------------ +; Remoting stub used to dispatch a method invocation. This is the choke point for all remoting calls; all +; scenarios where we determine we're not a local or a COM call, regardless of whether the dispatch is +; interface, virtual or direct will wind up here sooner or later. +; +; On entry: +; r0 : transparent proxy +; r12 : target MethodDesc or slot number +; plus user arguments in registers and on the stack +; + NESTED_ENTRY TransparentProxyStub_CrossContext + + PROLOG_WITH_TRANSITION_BLOCK 0x20 + + add r0, sp, #__PWTB_TransitionBlock ; pTransitionBlock + mov r1, r12 ; pMethodDesc + + bl TransparentProxyStubWorker + + ; r0 = fpRetSize + + ; return value is stored before float argument registers + add r1, sp, #(__PWTB_FloatArgumentRegisters - 0x20) + bl setStubReturnValue + + EPILOG_WITH_TRANSITION_BLOCK_RETURN + + NESTED_END + +; ------------------------------------------------------------------ +; This method does nothing. It's just a fixed function for the debugger to put a breakpoint on. + LEAF_ENTRY TransparentProxyStubPatch + add r0, r1, r2 +TransparentProxyStubPatchLabel + EXPORT TransparentProxyStubPatchLabel + bx lr + LEAF_END + +; ------------------------------------------------------------------ +; VSD helper for performing an in-context interface dispatch on a TransparentProxy. This only happens for +; ContextBoundObjects that are invoked in the correct context, never for general remoting. +; +; On entry: +; r0 : transparent proxy +; r12 : interface MethodDesc +; plus user arguments in registers and on the stack +; +; On exit: +; Tail calls to actual target which returns as normal to the caller. +; + NESTED_ENTRY InContextTPQuickDispatchAsmStub + + ; Spill caller's volatile argument registers and some other state we wish to preserve. + PROLOG_PUSH {r0-r3,r12,lr} + PROLOG_VPUSH {d0-d7} + + CHECK_STACK_ALIGNMENT + + ; Set up arguments for VSD_GetTargetForTPWorkerQuick + ; mov r0, r0 ; this + mov r1, r12 ; Interface MethodDesc + + bl VSD_GetTargetForTPWorkerQuick + + ; If we didn't find a target head for the slow path. + cbz r0, CacheMiss + + ; Save target address since we're about to restore the value of r0. Can't place it directly into r12 + ; since that's about to be restored as well. Instead we overwrite the saved version of r12 on the + ; stack (we don't need it any more since the lookup succeeded). + str r0, [sp, #((16 * 4) + (4 * 4))] + + ; Restore caller's argument registers. + EPILOG_VPOP {d0-d7} + EPILOG_POP {r0-r3,r12,lr} + + ; Tail call to the real code using the previously computed target address. + EPILOG_BRANCH_REG r12 + +CacheMiss + ; Restore caller's argument registers. + EPILOG_VPOP {d0-d7} + EPILOG_POP {r0-r3,r12,lr} + + EPILOG_BRANCH InContextTPDispatchAsmStub + + NESTED_END + +; ------------------------------------------------------------------ + + NESTED_ENTRY InContextTPDispatchAsmStub + + PROLOG_WITH_TRANSITION_BLOCK + + add r0, sp, #__PWTB_TransitionBlock ; pTransitionBlock + mov r1, r12 ; pMethodDesc / token + + bl VSD_GetTargetForTPWorker + + mov r12, r0 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + EPILOG_BRANCH_REG r12 + + NESTED_END + +; ------------------------------------------------------------------ +; Macro used to compare a MethodTable with that of __TransparentProxy. Sets the Z condition flag to indicate +; the result (Z=1 for a match, Z=0 for a mismatch). +; + MACRO + TP_TYPE_CHECK $methodTableReg, $scratchReg + + ldr $scratchReg, =$CTPMethodTable__s_pThunkTable + ldr $scratchReg, [$scratchReg] + cmp $scratchReg, $methodTableReg + MEND + +; ------------------------------------------------------------------ +; Macro used to perform a context check. +; +; Calls a user customizable routine that determines whether the current execution context warrants a context +; transition for the call. Regular remoting (as opposed to context transitioning based on ContextBoundObjects) +; always returns a context-mismatch from this call. +; +; On entry: +; r0 : this (TranparentProxy object) +; +; On exit: +; r0 : check result (0 == contexts match, non-zero == contexts mismatch) +; r1-r3,r12,lr: trashed +; + MACRO + TP_CONTEXT_CHECK + + ldr r1, [r0, #TransparentProxyObject___stub] + ldr r0, [r0, #TransparentProxyObject___stubData] + blx r1 + MEND + +; ------------------------------------------------------------------ +; Used by the remoting precode for non-virtual dispatch to instance methods which might be remoted. Performs a +; context and transparent proxy check and if both of these are negative (or the call has been made on a null +; 'this') we simply return and the precode will dispatch the call locally as normal. Otherwise we redirect to +; the remoting system and never return. +; +; On entry: +; r0 : this (may or may not be a TransparentProxy) +; r1 : trashed +; lr : return address into RemotingPrecode (RemotingPrecode* + REMOTING_PRECODE_RET_OFFSET) +; [sp, #0] : caller's saved r1 +; [sp, #4] : caller's saved lr (i.e. return address into caller of RemotingPrecode) +; plus user arguments in registers and on the stack +; + LEAF_ENTRY PrecodeRemotingThunk + + ; Send null 'this' case to local dispatch case (else we'd need to handle an A/V from this stub). + cbz r0, LocalDispatch ; predicted not taken + + ; Load MethodTable* in r12. + ldr r12, [r0] + + ; Compare MethodTable in 'this' with that of __TransparentProxy; if they're not equal we dispatch + ; locally. + TP_TYPE_CHECK r12, r1 ; r1 is a scratch register + beq TransparentProxyDispatch ; predicted not taken + +LocalDispatch + ; Recover target MethodDesc pointer from the RemotingPrecode (we have the address of this + + ; REMOTING_PRECODE_RET_OFFSET in lr). Subtract extra 1 to account for the low-bit being set in LR to + ; indicate thumb mode. + ; We do this here because even the local case needs r12 initialized. + ldr r12, [lr, #(RemotingPrecode__m_pMethodDesc - REMOTING_PRECODE_RET_OFFSET - 1)] + + bx lr + + LEAF_END + +; ------------------------------------------------------------------ +; Handles the atypical path for the remoting precode above (typically the non-local dispatch cases). The +; regular entry point defined by NESTED_ENTRY below is never called directly; it serves only to generate +; prolog unwind data matching the pushes of the caller's r1 and lr done in the remoting precode so we can +; unwind out of this frame. The real entry point is TransparentProxyDispatch called directly from +; PrecodeRemotingThunk. +; + NESTED_ENTRY TransparentProxyDispatch_FakeProlog + + ; Match what the remoting precode has pushed. + PROLOG_PUSH {r1,lr} + + ; This is where execution really starts. +TransparentProxyDispatch + + ; We need some temporary registers and to preserve lr. + PROLOG_PUSH {r0,r2-r5,lr} + + CHECK_STACK_ALIGNMENT + + ; Recover target MethodDesc pointer from the RemotingPrecode (we have the address of this + + ; REMOTING_PRECODE_RET_OFFSET in lr). Subtract extra 1 to account for the low-bit being set in LR to + ; indicate thumb mode. Stash the result in a non-volatile register to preserve it over the call to + ; TP_CONTEXT_CHECK below. + ldr r4, [lr, #(RemotingPrecode__m_pMethodDesc - REMOTING_PRECODE_RET_OFFSET - 1)] + + ; Check whether the TP is already in the correct context. This can happen for ContextBoundObjects + ; only. The following macro will trash volatile registers and lr and return the result in r0 (0 == + ; context match, non-zero for everything else). All other registers are preserved. + TP_CONTEXT_CHECK + + ; Place MethodDesc* in r12 ready for wherever we dispatch to next. + mov r12, r4 + + ; Check the result of TP_CONTEXT_CHECK + cbnz r0, ContextMismatch1 + + ; At this point we know we're being called on a transparent proxy but the source and destination + ; contexts match. This only happens for a ContextBoundObject. For an non-interface dispatch we can + ; just return to the local dispatch case; the precode will eventually redirect to the jitted code + ; which knows how to handle a TP-wrapped ContextBoundObject. For interface calls we need to hand off + ; to VSD so it can resolve to the real target method. The quickest way to determine which of these + ; cases we need is to look at the classification of the method desc. All interface methods for which a + ; remoting precode is used are marked as mcComInterop, which though non-intuitive is generally OK + ; since only COM interop and remoting can dispatch directly on an interface method desc. (Generic + ; interface methods are not classified as mcComInterop but we use a different mechanism to intercept + ; those). + ldrh r0, [r4, #MethodDesc__m_wFlags] + and r0, #MethodDesc__mdcClassification + cmp r0, #MethodDesc__mcComInterop + bne LocalDispatch1 + + ; Local interface dispatch case. Restore argument registers saved here and in the RemotingPrecode, + ; discard return address into the RemotingPrecode (we're not going back there) and restore the real + ; caller's return address to LR before tail calling into the interface dispatch helper. + EPILOG_POP {r0,r2-r5,lr} ; Restore arg registers saved by this routine and RemotingPrecode lr + EPILOG_POP {r1,lr} ; Restore r1 saved by RemotingPrecode and real return address + EPILOG_BRANCH InContextTPQuickDispatchAsmStub + +LocalDispatch1 + + ; Local dispatch case. Restore argument registers saved here and return to the remoting precode. + EPILOG_POP {r0,r2-r5,pc} + +ContextMismatch1 + ; Context-mismatch (remoted) dispatch case. Restore argument registers saved here and in the + ; RemotingPrecode, discard return address into the RemotingPrecode (we're not going back there) and + ; restore the real caller's return address to LR before tail calling into the cross-context helper. + EPILOG_POP {r0,r2-r5,lr} ; Restore arg registers saved by this routine and RemotingPrecode lr + EPILOG_POP {r1,lr} ; Restore r1 saved by RemotingPrecode and real return address + EPILOG_BRANCH TransparentProxyStub_CrossContext + + NESTED_END + +; ------------------------------------------------------------------ +; Used to dispatch an interface call that is possibly be cross-context or remoted. Normally this is handled +; by the remoting precode stub above but there is an edge case for generic interface methods that falls +; through the cracks (it is not easy to cover since the precode stub makes use of it as a quick means +; to differentiate between interface and non-interface calls in the non-cross context case). +; +; On entry: +; r0 : this (TransparentProxy object) +; r12 : interface MethodDesc +; plus user arguments in registers and on the stack +; +; On exit: +; Tail calls to the VSD in-context TP dispatcher or remoting system as appropriate. +; + NESTED_ENTRY CRemotingServices__DispatchInterfaceCall + + PROLOG_PUSH {r0-r3,r12,lr} + + CHECK_STACK_ALIGNMENT + + ; Check whether the TP is already in the correct context. This can happen for ContextBoundObjects + ; only. The following macro will trash volatile registers and lr and return the result in r0 (0 == + ; context match, non-zero for everything else). All other registers are preserved. + TP_CONTEXT_CHECK + cbnz r0, ContextMismatch2 + + ; Local interface dispatch case. Tail call to VSD helper specifically for the in-context TP dispatch + ; scenario. Interface MethodDesc is restored to r12. + EPILOG_POP {r0-r3,r12,lr} + EPILOG_BRANCH InContextTPQuickDispatchAsmStub + +ContextMismatch2 + ; Context-mismatch (remoted) dispatch case. Tail call to the general remoting dispatch code. Interface + ; MethodDesc is restored to r12. + EPILOG_POP {r0-r3,r12,lr} + EPILOG_BRANCH TransparentProxyStub_CrossContext + + NESTED_END + +; ------------------------------------------------------------------ +; Common stub used for vtable dispatch of remoted methods. A small prestub will load the vtable slot index +; into r12 and then jump here. This stub determines whether we're already in the correct context (which can +; only happen for ContextBoundObjects). Depending on the answers we'll either dispatch the call locally or +; re-direct it to the remoting system (via TransparentProxyStub_CrossContext). +; +; On entry: +; r0 : this (TransparentProxy object) +; r12 : virtual method slot number +; plus user arguments in registers and on the stack +; +; On exit: +; Tail calls to the VSD in-context TP dispatcher or remoting system as appropriate. +; + NESTED_ENTRY TransparentProxyStub + + PROLOG_PUSH {r0-r3,r12,lr} + + CHECK_STACK_ALIGNMENT + + ; Check whether the TP is already in the correct context. This can happen for ContextBoundObjects + ; only. The following macro will trash volatile registers and lr and return the result in r0 (0 == + ; context match, non-zero for everything else). All other registers are preserved. + TP_CONTEXT_CHECK + cbnz r0, ContextMismatch3 + + ; We need to perform a local vtable dispatch on the ContextBoundObject. Obviously this needs to be on + ; the real type held in the proxy, not TransparentProxy's MethodTable or we'll just end up back here + ; recursively. + + ; Recover 'this' pointer and slot number. + ldr r0, [sp] + ldr r12, [sp, #0x10] + + ; Extract real type from the TP. + ldr r0, [r0, #TransparentProxyObject___pMT] + + ; Vtables are no longer a linear array. Instead they use a two-level indirection with the first level + ; consisting of fixed sized chunks of function pointer arrays. R12 has our slot number. + + ; Calculate first level chunk index. + lsr r1, r12, #ASM__VTABLE_SLOTS_PER_CHUNK_LOG2 + + ; Load the address of the chunk from the MethodTable (the chunk table immediately follows the + ; MethodTable structure). + add r0, #SIZEOF__MethodTable + ldr r2, [r0, r1, lsl #2] + + ; Calculate the slot index within the chunk. + and r0, r12, #(ASM__VTABLE_SLOTS_PER_CHUNK - 1) + + ; Load the target address into r12 (we no longer need the slot number and we're about to restore the + ; other registers). + ldr r12, [r2, r0, lsl #2] + + ; Restore the stack state and tail call to the local target. + EPILOG_POP {r0-r3} + EPILOG_STACK_FREE 4 ; Skip restore of r12 since we've overwritten it + EPILOG_POP {lr} + EPILOG_BRANCH_REG r12 + +ContextMismatch3 + ; Contexts don't match so we have to dispatch through remoting. Clean up the stack and tail call to + ; the helper. + EPILOG_POP {r0-r3,r12,lr} + EPILOG_BRANCH TransparentProxyStub_CrossContext + + NESTED_END + +#endif // FEATURE_REMOTING +#if defined(FEATURE_REMOTING) || defined(FEATURE_COMINTEROP) +; ------------------------------------------------------------------ +; Function used by remoting/COM interop to get floating point return value (since it's not in the same +; register(s) as non-floating point values). +; +; On entry; +; r0 : size of the FP result (4 or 8 bytes) +; r1 : pointer to 64-bit buffer to receive result +; +; On exit: +; buffer pointed to by r1 on entry contains the float or double argument as appropriate +; + LEAF_ENTRY getFPReturn + + cmp r0, #4 + bne LgetFP8 + vmov r2, s0 + str r2, [r1] + bx lr +LgetFP8 + vmov r2, r3, d0 + strd r2, r3, [r1] + bx lr + + LEAF_END + +; ------------------------------------------------------------------ +; Function used by remoting/COM interop to set floating point return value (since it's not in the same +; register(s) as non-floating point values). +; +; On entry: +; r0 : size of the FP result (4 or 8 bytes) +; r2/r3 : 32-bit or 64-bit FP result +; +; On exit: +; s0 : float result if r0 == 4 +; d0 : double result if r0 == 8 +; + LEAF_ENTRY setFPReturn + + cmp r0, #4 + bne LsetFP8 + vmov s0, r2 + bx lr +LsetFP8 + vmov d0, r2, r3 + bx lr + + LEAF_END + +#endif defined(FEATURE_REMOTING) || defined(FEATURE_COMINTEROP) +#ifdef FEATURE_REMOTING + +; ------------------------------------------------------------------ +; Tail call Object.FieldGetter remotely with the given arguments. +; +; On entry: +; r0 : pMD (MethodDesc * of the Object.FieldGetter method) +; r1 : pThis (the transparent proxy) +; r2 : pFirst +; r3 : pSecond +; [sp, #0] : pThird +; +; On exit: +; Tail calls to the managed method +; + LEAF_ENTRY CRemotingServices__CallFieldGetter + + mov r12, r0 + mov r0, r1 + mov r1, r2 + mov r2, r3 + ldr r3, [sp, #0] + + b TransparentProxyStub_CrossContext + + LEAF_END + +; ------------------------------------------------------------------ +; Tail call Object.FieldSetter remotely with the given arguments. +; +; On entry: +; r0 : pMD (MethodDesc * of the Object.FieldSetter method) +; r1 : pThis (the transparent proxy) +; r2 : pFirst +; r3 : pSecond +; [sp, #0] : pThird +; +; On exit: +; Tail calls to the managed method +; + LEAF_ENTRY CRemotingServices__CallFieldSetter + + mov r12, r0 + mov r0, r1 + mov r1, r2 + mov r2, r3 + ldr r3, [sp, #0] + + b TransparentProxyStub_CrossContext + + LEAF_END + +; ------------------------------------------------------------------ +; General purpose remoting helper used to call given target with two parameters. +; +; On entry: +; r0 : pTarget +; r1 : pFirst +; r2 : pSecond +; +; + NESTED_ENTRY CTPMethodTable__CallTargetHelper2,,CallDescrWorkerUnwindFrameChainHandler + + PROLOG_PUSH {r11, lr} + + mov r12, r0 + mov r0, r1 + mov r1, r2 + + blx r12 + + ; Adding a nop so that unwind does not result in the IP being in epilog. + ; This ensures that the OS unwinder looks up the personality routine for this method. + nop + + EPILOG_POP {r11, pc} + + NESTED_END + +; ------------------------------------------------------------------ +; General purpose remoting helper used to call given target with three parameters. +; +; On entry: +; r0 : pTarget +; r1 : pFirst +; r2 : pSecond +; r3 : pThird +; +; + NESTED_ENTRY CTPMethodTable__CallTargetHelper3,,CallDescrWorkerUnwindFrameChainHandler + + PROLOG_PUSH {r11, lr} + + mov r12, r0 + mov r0, r1 + mov r1, r2 + mov r2, r3 + + blx r12 + + ; Adding a nop so that unwind does not result in the IP being in epilog. + ; This ensures that the OS unwinder looks up the personality routine for this method. + nop + + EPILOG_POP {r11, pc} + + NESTED_END + +#endif // FEATURE_REMOTING + +#ifdef FEATURE_COMINTEROP +; ------------------------------------------------------------------ +; GenericComPlusCallStub that erects a ComPlusMethodFrame and calls into the runtime +; (CLRToCOMWorker) to dispatch rare cases of the interface call. +; +; On entry: +; r0 : 'this' object +; r12 : Interface MethodDesc* +; plus user arguments in registers and on the stack +; +; On exit: +; r0/r1/s0/d0 set to return value of the call as appropriate +; + NESTED_ENTRY GenericComPlusCallStub + + PROLOG_WITH_TRANSITION_BLOCK 0x20 + + add r0, sp, #__PWTB_TransitionBlock ; pTransitionBlock + mov r1, r12 ; pMethodDesc + + ; Call CLRToCOMWorker(pFrame). This call will set up the rest of the frame (including the vfptr, + ; the GS cookie and linking to the thread), make the client call and return with correct registers set + ; (r0/r1/s0-s3/d0-d3 as appropriate). + + bl CLRToCOMWorker + + ; r0 = fpRetSize + + ; return value is stored before float argument registers + add r1, sp, #(__PWTB_FloatArgumentRegisters - 0x20) + bl setStubReturnValue + + EPILOG_WITH_TRANSITION_BLOCK_RETURN + + NESTED_END + +; ------------------------------------------------------------------ +; COM to CLR stub called the first time a particular method is invoked. +; +; On entry: +; r12 : (MethodDesc* - ComCallMethodDesc_Offset_FromR12) provided by prepad thunk +; plus user arguments in registers and on the stack +; +; On exit: +; tail calls to real method +; + NESTED_ENTRY ComCallPreStub + + GBLA ComCallPreStub_FrameSize + GBLA ComCallPreStub_FramePad + GBLA ComCallPreStub_StackAlloc + GBLA ComCallPreStub_Frame + GBLA ComCallPreStub_ErrorReturn + +; Set the defaults +ComCallPreStub_FramePad SETA 8 ; error return +ComCallPreStub_FrameSize SETA (ComCallPreStub_FramePad + SIZEOF__GSCookie + SIZEOF__ComMethodFrame) + + IF ComCallPreStub_FrameSize:MOD:8 != 0 +ComCallPreStub_FramePad SETA ComCallPreStub_FramePad + 4 +ComCallPreStub_FrameSize SETA ComCallPreStub_FrameSize + 4 + ENDIF + +ComCallPreStub_StackAlloc SETA ComCallPreStub_FrameSize - SIZEOF__ArgumentRegisters - 2 * 4 +ComCallPreStub_Frame SETA SIZEOF__FloatArgumentRegisters + ComCallPreStub_FramePad + SIZEOF__GSCookie +ComCallPreStub_ErrorReturn SETA SIZEOF__FloatArgumentRegisters + + PROLOG_PUSH {r0-r3} ; Spill general argument registers + PROLOG_PUSH {r11,lr} ; Save return address + PROLOG_STACK_ALLOC ComCallPreStub_StackAlloc ; Alloc non-spill portion of stack frame + PROLOG_VPUSH {d0-d7} ; Spill floating point argument registers + + CHECK_STACK_ALIGNMENT + + ; Finish initializing the frame. The C++ helper will fill in the GS cookie and vfptr and link us to + ; the Thread frame chain (see ComPrestubMethodFrame::Push). That leaves us with m_pFuncDesc. + ; The prepad thunk passes us a value which is the MethodDesc* - ComCallMethodDesc_Offset_FromR12 (due to encoding limitations in the + ; thunk). So we must correct this by adding 4 before storing the pointer. + add r12, #(ComCallMethodDesc_Offset_FromR12) + str r12, [sp, #(ComCallPreStub_Frame + UnmanagedToManagedFrame__m_pvDatum)] + + ; Call the C++ worker: ComPreStubWorker(&Frame) + add r0, sp, #(ComCallPreStub_Frame) + add r1, sp, #(ComCallPreStub_ErrorReturn) + bl ComPreStubWorker + + ; Handle failure case. + cbz r0, ErrorExit + + ; Stash real target address where it won't be overwritten by restoring the calling state. + mov r12, r0 + + EPILOG_VPOP {d0-d7} ; Restore floating point argument registers + EPILOG_STACK_FREE ComCallPreStub_StackAlloc + EPILOG_POP {r11,lr} + EPILOG_POP {r0-r3} ; Restore argument registers + ; Tail call the real target. Actually ComPreStubWorker returns the address of the prepad thunk on ARM, + ; that way we don't run out of volatile registers trying to remember both the new target address and + ; the hidden MethodDesc* argument. ComPreStubWorker patched the prepad though so the second time + ; through we won't end up here again. + EPILOG_BRANCH_REG r12 + +ErrorExit + ; Failed to find a stub to call. Retrieve the return value ComPreStubWorker set for us. + ldr r0, [sp, #(ComCallPreStub_ErrorReturn)] + ldr r1, [sp, #(ComCallPreStub_ErrorReturn+4)] + EPILOG_STACK_FREE ComCallPreStub_StackAlloc + SIZEOF__FloatArgumentRegisters + EPILOG_POP {r11,lr} + EPILOG_STACK_FREE SIZEOF__ArgumentRegisters + EPILOG_RETURN + + NESTED_END + +; ------------------------------------------------------------------ +; COM to CLR stub which sets up a ComMethodFrame and calls COMToCLRWorker. +; +; On entry: +; r12 : (MethodDesc* - ComCallMethodDesc_Offset_FromR12) provided by prepad thunk +; plus user arguments in registers and on the stack +; +; On exit: +; Result in r0/r1/s0/d0 as per the real method being called +; + NESTED_ENTRY GenericComCallStub,,ReverseComUnwindFrameChainHandler + +; Calculate space needed on stack for alignment padding, a GS cookie and a ComMethodFrame (minus the last +; field, m_ReturnAddress, which we'll push explicitly). + + GBLA GenericComCallStub_FrameSize + GBLA GenericComCallStub_FramePad + GBLA GenericComCallStub_StackAlloc + GBLA GenericComCallStub_Frame + +; Set the defaults +GenericComCallStub_FramePad SETA 0 +GenericComCallStub_FrameSize SETA (GenericComCallStub_FramePad + SIZEOF__GSCookie + SIZEOF__ComMethodFrame) + + IF GenericComCallStub_FrameSize:MOD:8 != 0 +GenericComCallStub_FramePad SETA 4 +GenericComCallStub_FrameSize SETA GenericComCallStub_FrameSize + GenericComCallStub_FramePad + ENDIF + +GenericComCallStub_StackAlloc SETA GenericComCallStub_FrameSize - SIZEOF__ArgumentRegisters - 2 * 4 +GenericComCallStub_Frame SETA SIZEOF__FloatArgumentRegisters + GenericComCallStub_FramePad + SIZEOF__GSCookie + + PROLOG_PUSH {r0-r3} ; Spill general argument registers + PROLOG_PUSH {r11,lr} ; Save return address + PROLOG_STACK_ALLOC GenericComCallStub_StackAlloc ; Alloc non-spill portion of stack frame + PROLOG_VPUSH {d0-d7} ; Spill floating point argument registers + + CHECK_STACK_ALIGNMENT + + ; Store MethodDesc* in frame. Due to a limitation of the prepad, r12 actually contains a value + ; "ComCallMethodDesc_Offset_FromR12" less than the pointer we want, so fix that up. + add r12, r12, #(ComCallMethodDesc_Offset_FromR12) + str r12, [sp, #(GenericComCallStub_Frame + UnmanagedToManagedFrame__m_pvDatum)] + + ; Call COMToCLRWorker(pThread, pFrame). Note that pThread is computed inside the method so we don't + ; need to set it up here. + ; + ; Setup R1 to point to the start of the explicit frame. We account for alignment padding and + ; space for GSCookie. + add r1, sp, #(GenericComCallStub_Frame) + bl COMToCLRWorker + + EPILOG_STACK_FREE GenericComCallStub_StackAlloc + SIZEOF__FloatArgumentRegisters + EPILOG_POP {r11,lr} + EPILOG_STACK_FREE SIZEOF__ArgumentRegisters + EPILOG_RETURN + + NESTED_END + +; ------------------------------------------------------------------ +; COM to CLR stub called from COMToCLRWorker that actually dispatches to the real managed method. +; +; On entry: +; r0 : dwStackSlots, count of argument stack slots to copy +; r1 : pFrame, ComMethodFrame pushed by GenericComCallStub above +; r2 : pTarget, address of code to call +; r3 : pSecretArg, hidden argument passed to target above in r12 +; [sp, #0] : pDangerousThis, managed 'this' reference +; +; On exit: +; Result in r0/r1/s0/d0 as per the real method being called +; + NESTED_ENTRY COMToCLRDispatchHelper,,CallDescrWorkerUnwindFrameChainHandler + + PROLOG_PUSH {r4-r5,r7,lr} + PROLOG_STACK_SAVE r7 + + ; Copy stack-based arguments. Make sure the eventual SP ends up 8-byte aligned. Note that the + ; following calculations assume that the prolog has left the stack already aligned. + CHECK_STACK_ALIGNMENT + + cbz r0, COMToCLRDispatchHelper_ArgumentsSetup + + lsl r4, r0, #2 ; r4 = (dwStackSlots * 4) + and r5, r4, #4 ; Align the stack + sub sp, sp, r5 + + add r5, r1, #SIZEOF__ComMethodFrame + add r5, r5, r4 + +COMToCLRDispatchHelper_StackLoop + ldr r4, [r5,#-4]! + str r4, [sp,#-4]! + subs r0, r0, #1 + bne COMToCLRDispatchHelper_StackLoop + + CHECK_STACK_ALIGNMENT + +COMToCLRDispatchHelper_ArgumentsSetup + ; Load floating point argument registers. + sub r4, r1, #(GenericComCallStub_Frame) + vldm r4, {d0-d7} + + ; Prepare the call target and hidden argument prior to overwriting r0-r3. + mov r12, r3 ; r12 = hidden argument + mov lr, r2 ; lr = target code + + ; Load general argument registers except r0. + add r4, r1, #(SIZEOF__ComMethodFrame - SIZEOF__ArgumentRegisters + 4) + ldm r4, {r1-r3} + + ; Load r0 from the managed this, not the original incoming IUnknown*. + ldr r0, [r7, #(4 * 4)] + + ; Make the call. + blx lr + + EPILOG_STACK_RESTORE r7 + EPILOG_POP {r4-r5,r7,pc} + + NESTED_END + +#endif // FEATURE_COMINTEROP + +#ifdef PROFILING_SUPPORTED + +PROFILE_ENTER equ 1 +PROFILE_LEAVE equ 2 +PROFILE_TAILCALL equ 4 + + ; Define the layout of the PROFILE_PLATFORM_SPECIFIC_DATA we push on the stack for all profiler + ; helpers. + map 0 + field 4 ; r0 + field 4 ; r1 + field 4 ; r11 + field 4 ; Pc (caller's PC, i.e. LR) + field SIZEOF__FloatArgumentRegisters ; spilled floating point argument registers +functionId field 4 +probeSp field 4 +profiledSp field 4 +hiddenArg field 4 +flags field 4 + +SIZEOF__PROFILE_PLATFORM_SPECIFIC_DATA field 0 + +; ------------------------------------------------------------------ +; Macro used to generate profiler helpers. In all cases we push a partially initialized +; PROFILE_PLATFORM_SPECIFIC_DATA structure on the stack and call into a C++ helper to continue processing. +; +; On entry: +; r0 : clientInfo +; r1/r2 : return values (in case of leave) +; frame pointer(r11) must be set (in case of enter) +; all arguments are on stack at frame pointer (r11) + 8bytes (save lr & prev r11). +; +; On exit: +; All register values are preserved including volatile registers +; + MACRO + DefineProfilerHelper $HelperName, $Flags + + GBLS __ProfilerHelperFunc +__ProfilerHelperFunc SETS "$HelperName":CC:"Naked" + + NESTED_ENTRY $__ProfilerHelperFunc + + IMPORT $HelperName ; The C++ helper which does most of the work + + PROLOG_PUSH {r0,r3,r9,r12} ; save volatile general purpose registers. remaining r1 & r2 are saved below...saving r9 as it is required for virtualunwinding + PROLOG_STACK_ALLOC (6*4) ; Reserve space for tail end of structure (5*4 bytes) and extra 4 bytes is for aligning the stack at 8-byte boundary + PROLOG_VPUSH {d0-d7} ; Spill floting point argument registers + PROLOG_PUSH {r1,r11,lr} ; Save possible return value in r1, frame pointer and return address + PROLOG_PUSH {r2} ; Save possible return value in r0. Before calling Leave Hook Jit moves contents of r0 to r2 + ; so pushing r2 instead of r0. This push statement cannot be combined with the above push + ; as r2 gets pushed before r1. + + CHECK_STACK_ALIGNMENT + + ; Zero r1 for use clearing fields in the PROFILE_PLATFORM_SPECIFIC_DATA. + eor r1, r1 + + ; Clear functionId. + str r1, [sp, #functionId] + + ; Save caller's SP (at the point this helper was called). + add r2, sp, #(SIZEOF__PROFILE_PLATFORM_SPECIFIC_DATA + 20) + str r2, [sp, #probeSp] + + ; Save caller's SP (at the point where only argument registers have been spilled). + ldr r2, [r11] + add r2, r2, #8 ; location of arguments is at frame pointer(r11) + 8 (lr & prev frame ptr is saved before changing + str r2, [sp, #profiledSp] + + ; Clear hiddenArg. + str r1, [sp, #hiddenArg] + + ; Set flags to indicate type of helper called. + mov r1, #($Flags) + str r1, [sp, #flags] + + ; Call C++ portion of helper (<$HelperName>(clientInfo, &profilePlatformSpecificData)). + mov r1, sp + bl $HelperName + + EPILOG_POP {r2} + EPILOG_POP {r1,r11,lr} + EPILOG_VPOP {d0-d7} + EPILOG_STACK_FREE (6*4) + EPILOG_POP {r0,r3,r9,r12} + + EPILOG_RETURN + + NESTED_END + + MEND + + DefineProfilerHelper ProfileEnter, PROFILE_ENTER + DefineProfilerHelper ProfileLeave, PROFILE_LEAVE + DefineProfilerHelper ProfileTailcall, PROFILE_TAILCALL + +#endif // PROFILING_SUPPORTED + + ; + ; If a preserved register were pushed onto the stack between + ; the managed caller and the H_M_F, _R4_R11 will point to its + ; location on the stack and it would have been updated on the + ; stack by the GC already and it will be popped back into the + ; appropriate register when the appropriate epilog is run. + ; + ; Otherwise, the register is preserved across all the code + ; in this HCALL or FCALL, so we need to update those registers + ; here because the GC will have updated our copies in the + ; frame. + ; + ; So, if _R4_R11 points into the MachState, we need to update + ; the register here. That's what this macro does. + ; + + MACRO + RestoreRegMS $regIndex, $reg + + ; Incoming: + ; + ; R0 = address of MachState + ; + ; $regIndex: Index of the register (R4-R11). For R4, index is 4. + ; For R5, index is 5, and so on. + ; + ; $reg: Register name (e.g. R4, R5, etc) + ; + ; Get the address of the specified captured register from machine state + add r2, r0, #(MachState__captureR4_R11 + (($regIndex-4)*4)) + + ; Get the address of the specified preserved register from machine state + ldr r3, [r0, #(MachState___R4_R11 + (($regIndex-4)*4))] + + cmp r2, r3 + bne %FT0 + ldr $reg, [r2] +0 + + MEND + +; EXTERN_C int __fastcall HelperMethodFrameRestoreState( +; INDEBUG_COMMA(HelperMethodFrame *pFrame) +; MachState *pState +; ) + LEAF_ENTRY HelperMethodFrameRestoreState + +#ifdef _DEBUG + mov r0, r1 +#endif + + ; If machine state is invalid, then simply exit + ldr r1, [r0, #MachState__isValid] + cmp r1, #0 + beq Done + + RestoreRegMS 4, R4 + RestoreRegMS 5, R5 + RestoreRegMS 6, R6 + RestoreRegMS 7, R7 + RestoreRegMS 8, R8 + RestoreRegMS 9, R9 + RestoreRegMS 10, R10 + RestoreRegMS 11, R11 +Done + ; Its imperative that the return value of HelperMethodFrameRestoreState is zero + ; as it is used in the state machine to loop until it becomes zero. + ; Refer to HELPER_METHOD_FRAME_END macro for details. + mov r0,#0 + bx lr + + LEAF_END + +#ifdef FEATURE_HIJACK + +; ------------------------------------------------------------------ +; Hijack function for functions which return a value type + NESTED_ENTRY OnHijackTripThread + PROLOG_PUSH {r0,r4-r11,lr} + + PROLOG_VPUSH {d0-d3} ; saving as d0-d3 can have the floating point return value + PROLOG_PUSH {r1} ; saving as r1 can have partial return value when return is > 32 bits + PROLOG_STACK_ALLOC 4 ; 8 byte align + + CHECK_STACK_ALIGNMENT + + add r0, sp, #40 + bl OnHijackWorker + + EPILOG_STACK_FREE 4 + EPILOG_POP {r1} + EPILOG_VPOP {d0-d3} + + EPILOG_POP {r0,r4-r11,pc} + NESTED_END + +#endif ; FEATURE_HIJACK + +; ------------------------------------------------------------------ +; Macro to generate Redirection Stubs +; +; $reason : reason for redirection +; Eg. GCThreadControl +; NOTE: If you edit this macro, make sure you update GetCONTEXTFromRedirectedStubStackFrame. +; This function is used by both the personality routine and the debugger to retrieve the original CONTEXT. + MACRO + GenerateRedirectedHandledJITCaseStub $reason + + GBLS __RedirectionStubFuncName + GBLS __RedirectionStubEndFuncName + GBLS __RedirectionFuncName +__RedirectionStubFuncName SETS "RedirectedHandledJITCaseFor":CC:"$reason":CC:"_Stub" +__RedirectionStubEndFuncName SETS "RedirectedHandledJITCaseFor":CC:"$reason":CC:"_StubEnd" +__RedirectionFuncName SETS "|?RedirectedHandledJITCaseFor":CC:"$reason":CC:"@Thread@@CAXXZ|" + + IMPORT $__RedirectionFuncName + + NESTED_ENTRY $__RedirectionStubFuncName + + PROLOG_PUSH {r7,lr} ; return address + PROLOG_STACK_ALLOC 4 ; stack slot to save the CONTEXT * + PROLOG_STACK_SAVE r7 + + ;REDIRECTSTUB_SP_OFFSET_CONTEXT is defined in asmconstants.h + ;If CONTEXT is not saved at 0 offset from SP it must be changed as well. + ASSERT REDIRECTSTUB_SP_OFFSET_CONTEXT == 0 + + ; Runtime check for 8-byte alignment. This check is necessary as this function can be + ; entered before complete execution of the prolog of another function. + and r0, r7, #4 + sub sp, sp, r0 + + ; stack must be 8 byte aligned + CHECK_STACK_ALIGNMENT + + ; + ; Save a copy of the redirect CONTEXT*. + ; This is needed for the debugger to unwind the stack. + ; + bl GetCurrentSavedRedirectContext + str r0, [r7] + + ; + ; Fetch the interrupted pc and save it as our return address. + ; + ldr r1, [r0, #CONTEXT_Pc] + str r1, [r7, #8] + + ; + ; Call target, which will do whatever we needed to do in the context + ; of the target thread, and will RtlRestoreContext when it is done. + ; + bl $__RedirectionFuncName + + EMIT_BREAKPOINT ; Unreachable + +; Put a label here to tell the debugger where the end of this function is. +$__RedirectionStubEndFuncName + EXPORT $__RedirectionStubEndFuncName + + NESTED_END + + MEND + +; ------------------------------------------------------------------ +; Redirection Stub for GC in fully interruptible method + GenerateRedirectedHandledJITCaseStub GCThreadControl +; ------------------------------------------------------------------ + GenerateRedirectedHandledJITCaseStub DbgThreadControl +; ------------------------------------------------------------------ + GenerateRedirectedHandledJITCaseStub UserSuspend +; ------------------------------------------------------------------ + GenerateRedirectedHandledJITCaseStub YieldTask + +#ifdef _DEBUG +; ------------------------------------------------------------------ +; Redirection Stub for GC Stress + GenerateRedirectedHandledJITCaseStub GCStress +#endif + +; ------------------------------------------------------------------ +; Functions to probe for stack space +; Input reg r4 = amount of stack to probe for +; value of reg r4 is preserved on exit from function +; r12 is trashed +; The below two functions were copied from vctools\crt\crtw32\startup\arm\chkstk.asm + + NESTED_ENTRY checkStack + subs r12,sp,r4 + mrc p15,#0,r4,c13,c0,#2 ; get TEB * + ldr r4,[r4,#8] ; get Stack limit + bcc checkStack_neg ; if r12 is less then 0 set it to 0 +checkStack_label1 + cmp r12, r4 + bcc stackProbe ; must probe to extend guardpage if r12 is beyond stackLimit + sub r4, sp, r12 ; restore value of r4 + EPILOG_RETURN +checkStack_neg + mov r12, #0 + b checkStack_label1 + NESTED_END + + NESTED_ENTRY stackProbe + PROLOG_PUSH {r5,r6} + mov r6, r12 + bfc r6, #0, #0xc ; align down (4K) +stackProbe_loop + sub r4,r4,#0x1000 ; dec stack Limit by 4K as page size is 4K + ldr r5,[r4] ; try to read ... this should move the guard page + cmp r4,r6 + bne stackProbe_loop + EPILOG_POP {r5,r6} + EPILOG_NOP sub r4,sp,r12 + EPILOG_RETURN + NESTED_END + +;------------------------------------------------ +; VirtualMethodFixupStub +; +; In NGEN images, virtual slots inherited from cross-module dependencies +; point to a jump thunk that calls into the following function that will +; call into a VM helper. The VM helper is responsible for patching up +; thunk, upon executing the precode, so that all subsequent calls go directly +; to the actual method body. +; +; This is done lazily for performance reasons. +; +; On entry: +; +; R0 = "this" pointer +; R12 = Address of thunk + 4 + + NESTED_ENTRY VirtualMethodFixupStub + + ; Save arguments and return address + PROLOG_PUSH {r0-r3, lr} + + ; Align stack + PROLOG_STACK_ALLOC SIZEOF__FloatArgumentRegisters + 4 + vstm sp, {d0-d7} + + + CHECK_STACK_ALIGNMENT + + ; R12 contains an address that is 4 bytes ahead of + ; where the thunk starts. Refer to ZapImportVirtualThunk::Save + ; for details on this. + ; + ; Move the correct thunk start address in R1 + sub r1, r12, #4 + + ; Call the helper in the VM to perform the actual fixup + ; and tell us where to tail call. R0 already contains + ; the this pointer. + bl VirtualMethodFixupWorker + + ; On return, R0 contains the target to tailcall to + mov r12, r0 + + ; pop the stack and restore original register state + vldm sp, {d0-d7} + EPILOG_STACK_FREE SIZEOF__FloatArgumentRegisters + 4 + EPILOG_POP {r0-r3, lr} + + PATCH_LABEL VirtualMethodFixupPatchLabel + + ; and tailcall to the actual method + EPILOG_BRANCH_REG r12 + + NESTED_END + +;------------------------------------------------ +; ExternalMethodFixupStub +; +; In NGEN images, calls to cross-module external methods initially +; point to a jump thunk that calls into the following function that will +; call into a VM helper. The VM helper is responsible for patching up the +; thunk, upon executing the precode, so that all subsequent calls go directly +; to the actual method body. +; +; This is done lazily for performance reasons. +; +; On entry: +; +; R12 = Address of thunk + 4 + + NESTED_ENTRY ExternalMethodFixupStub + + PROLOG_WITH_TRANSITION_BLOCK + + add r0, sp, #__PWTB_TransitionBlock ; pTransitionBlock + + ; Adjust (read comment above for details) and pass the address of the thunk + sub r1, r12, #4 ; pThunk + + mov r2, #0 ; sectionIndex + mov r3, #0 ; pModule + bl ExternalMethodFixupWorker + + ; mov the address we patched to in R12 so that we can tail call to it + mov r12, r0 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + PATCH_LABEL ExternalMethodFixupPatchLabel + EPILOG_BRANCH_REG r12 + + NESTED_END + +;------------------------------------------------ +; StubDispatchFixupStub +; +; In NGEN images, calls to interface methods initially +; point to a jump thunk that calls into the following function that will +; call into a VM helper. The VM helper is responsible for patching up the +; thunk with actual stub dispatch stub. +; +; On entry: +; +; R4 = Address of indirection cell + + NESTED_ENTRY StubDispatchFixupStub + + PROLOG_WITH_TRANSITION_BLOCK + + ; address of StubDispatchFrame + add r0, sp, #__PWTB_TransitionBlock ; pTransitionBlock + mov r1, r4 ; siteAddrForRegisterIndirect + mov r2, #0 ; sectionIndex + mov r3, #0 ; pModule + + bl StubDispatchFixupWorker + + ; mov the address we patched to in R12 so that we can tail call to it + mov r12, r0 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + PATCH_LABEL StubDispatchFixupPatchLabel + EPILOG_BRANCH_REG r12 + + NESTED_END + +;------------------------------------------------ +; JIT_RareDisableHelper +; +; The JIT expects this helper to preserve registers used for return values +; + NESTED_ENTRY JIT_RareDisableHelper + + PROLOG_PUSH {r0-r1, r11, lr} ; save integer return value + PROLOG_VPUSH {d0-d3} ; floating point return value + + CHECK_STACK_ALIGNMENT + + bl JIT_RareDisableHelperWorker + + EPILOG_VPOP {d0-d3} + EPILOG_POP {r0-r1, r11, pc} + + NESTED_END + + +#ifdef FEATURE_CORECLR +; +; JIT Static access helpers for single appdomain case +; + +; ------------------------------------------------------------------ +; void* JIT_GetSharedNonGCStaticBase(SIZE_T moduleDomainID, DWORD dwClassDomainID) + + LEAF_ENTRY JIT_GetSharedNonGCStaticBase_SingleAppDomain + + ; If class is not initialized, bail to C++ helper + add r2, r0, #DomainLocalModule__m_pDataBlob + ldrb r2, [r2, r1] + tst r2, #1 + beq CallCppHelper1 + + bx lr + +CallCppHelper1 + ; Tail call JIT_GetSharedNonGCStaticBase_Helper + b JIT_GetSharedNonGCStaticBase_Helper + LEAF_END + + +; ------------------------------------------------------------------ +; void* JIT_GetSharedNonGCStaticBaseNoCtor(SIZE_T moduleDomainID, DWORD dwClassDomainID) + + LEAF_ENTRY JIT_GetSharedNonGCStaticBaseNoCtor_SingleAppDomain + + bx lr + LEAF_END + + +; ------------------------------------------------------------------ +; void* JIT_GetSharedGCStaticBase(SIZE_T moduleDomainID, DWORD dwClassDomainID) + + LEAF_ENTRY JIT_GetSharedGCStaticBase_SingleAppDomain + + ; If class is not initialized, bail to C++ helper + add r2, r0, #DomainLocalModule__m_pDataBlob + ldrb r2, [r2, r1] + tst r2, #1 + beq CallCppHelper3 + + ldr r0, [r0, #DomainLocalModule__m_pGCStatics] + bx lr + +CallCppHelper3 + ; Tail call Jit_GetSharedGCStaticBase_Helper + b JIT_GetSharedGCStaticBase_Helper + LEAF_END + + +; ------------------------------------------------------------------ +; void* JIT_GetSharedGCStaticBaseNoCtor(SIZE_T moduleDomainID, DWORD dwClassDomainID) + + LEAF_ENTRY JIT_GetSharedGCStaticBaseNoCtor_SingleAppDomain + + ldr r0, [r0, #DomainLocalModule__m_pGCStatics] + bx lr + LEAF_END + +#endif + +; ------------------------------------------------------------------ +; __declspec(naked) void F_CALL_CONV JIT_Stelem_Ref(PtrArray* array, unsigned idx, Object* val) + LEAF_ENTRY JIT_Stelem_Ref + + ; We retain arguments as they were passed and use r0 == array; r1 == idx; r2 == val + + ; check for null array + cbz r0, ThrowNullReferenceException + + ; idx bounds check + ldr r3,[r0,#ArrayBase__m_NumComponents] + cmp r3,r1 + bls ThrowIndexOutOfRangeException + + ; fast path to null assignment (doesn't need any write-barriers) + cbz r2, AssigningNull + + ; Verify the array-type and val-type matches before writing + ldr r12, [r0] ; r12 = array MT + ldr r3, [r2] ; r3 = val->GetMethodTable() + ldr r12, [r12, #MethodTable__m_ElementType] ; array->GetArrayElementTypeHandle() + cmp r3, r12 + beq JIT_Stelem_DoWrite + + ; Types didnt match but allow writing into an array of objects + ldr r3, =$g_pObjectClass + ldr r3, [r3] ; r3 = *g_pObjectClass + cmp r3, r12 ; array type matches with Object* + beq JIT_Stelem_DoWrite + + ; array type and val type do not exactly match. Raise frame and do detailed match + b JIT_Stelem_Ref_NotExactMatch + +AssigningNull + ; Assigning null doesn't need write barrier + adds r0, r1, LSL #2 ; r0 = r0 + (r1 x 4) = array->m_array[idx] + str r2, [r0, #PtrArray__m_Array] ; array->m_array[idx] = val + bx lr + +ThrowNullReferenceException + ; Tail call JIT_InternalThrow(NullReferenceException) + ldr r0, =CORINFO_NullReferenceException_ASM + b JIT_InternalThrow + +ThrowIndexOutOfRangeException + ; Tail call JIT_InternalThrow(NullReferenceException) + ldr r0, =CORINFO_IndexOutOfRangeException_ASM + b JIT_InternalThrow + + LEAF_END + +; ------------------------------------------------------------------ +; __declspec(naked) void F_CALL_CONV JIT_Stelem_Ref_NotExactMatch(PtrArray* array, +; unsigned idx, Object* val) +; r12 = array->GetArrayElementTypeHandle() +; + NESTED_ENTRY JIT_Stelem_Ref_NotExactMatch + PROLOG_PUSH {lr} + PROLOG_PUSH {r0-r2} + + CHECK_STACK_ALIGNMENT + + ; allow in case val can be casted to array element type + ; call ObjIsInstanceOfNoGC(val, array->GetArrayElementTypeHandle()) + mov r1, r12 ; array->GetArrayElementTypeHandle() + mov r0, r2 + bl ObjIsInstanceOfNoGC + cmp r0, TypeHandle_CanCast + beq DoWrite ; ObjIsInstance returned TypeHandle::CanCast + + ; check via raising frame +NeedFrame + mov r1, sp ; r1 = &array + adds r0, sp, #8 ; r0 = &val + bl ArrayStoreCheck ; ArrayStoreCheck(&val, &array) + +DoWrite + EPILOG_POP {r0-r2} + EPILOG_POP {lr} + EPILOG_BRANCH JIT_Stelem_DoWrite + + NESTED_END + +; ------------------------------------------------------------------ +; __declspec(naked) void F_CALL_CONV JIT_Stelem_DoWrite(PtrArray* array, unsigned idx, Object* val) + LEAF_ENTRY JIT_Stelem_DoWrite + + ; Setup args for JIT_WriteBarrier. r0 = &array->m_array[idx]; r1 = val + adds r0, #PtrArray__m_Array ; r0 = &array->m_array + adds r0, r1, LSL #2 + mov r1, r2 ; r1 = val + + ; Branch to the write barrier (which is already correctly overwritten with + ; single or multi-proc code based on the current CPU + b JIT_WriteBarrier + + LEAF_END + +; ------------------------------------------------------------------ +; GC write barrier support. +; +; There's some complexity here for a couple of reasons: +; +; Firstly, there are a few variations of barrier types (input registers, checked vs unchecked, UP vs MP etc.). +; So first we define a number of helper macros that perform fundamental pieces of a barrier and then we define +; the final barrier functions by assembling these macros in various combinations. +; +; Secondly, for performance reasons we believe it's advantageous to be able to modify the barrier functions +; over the lifetime of the CLR. Specifically ARM has real problems reading the values of external globals (we +; need two memory indirections to do this) so we'd like to be able to directly set the current values of +; various GC globals (e.g. g_lowest_address and g_card_table) into the barrier code itself and then reset them +; every time they change (the GC already calls the VM to inform it of these changes). The handle this without +; creating too much fragility such as hardcoding instruction offsets in the VM update code, we wrap write +; barrier creation and GC globals access in a set of macros that create a table of descriptors describing each +; offset that must be patched. +; + +; Many of the following macros need a scratch register. Define a name for it here so it's easy to modify this +; in the future. + GBLS __wbscratch +__wbscratch SETS "r3" + +; +; First define the meta-macros used to support dynamically patching write barriers. +; + + ; WRITEBARRIERAREA + ; + ; As we assemble each write barrier function we build a descriptor for the offsets within that function + ; that need to be patched at runtime. We write these descriptors into a read-only portion of memory. Use a + ; specially-named linker section for this to ensure all the descriptors are contiguous and form a table. + ; During the final link of the CLR this section should be merged into the regular read-only data section. + ; + ; This macro handles switching assembler output to the above section (similar to the TEXTAREA or + ; RODATAAREA macros defined by kxarm.h). + ; + MACRO + WRITEBARRIERAREA + AREA |.clrwb|,DATA,READONLY + MEND + + ; BEGIN_WRITE_BARRIERS + ; + ; This macro must be invoked before any write barriers are defined. It sets up and exports a symbol, + ; g_rgWriteBarrierDescriptors, used by the VM to locate the start of the table describing the offsets in + ; each write barrier that need to be modified dynamically. + ; + MACRO + BEGIN_WRITE_BARRIERS + + ; Define a global boolean to track whether we're currently in a BEGIN_WRITE_BARRIERS section. This is + ; used purely to catch incorrect attempts to define a write barrier outside the section. + GBLL __defining_write_barriers +__defining_write_barriers SETL {true} + + ; Switch to the descriptor table section. + WRITEBARRIERAREA + + ; Define and export a symbol pointing to the start of the descriptor table. +g_rgWriteBarrierDescriptors + EXPORT g_rgWriteBarrierDescriptors + + ; Switch back to the code section. + TEXTAREA + MEND + + ; END_WRITE_BARRIERS + ; + ; This macro must be invoked after all write barriers have been defined. It finalizes the creation of the + ; barrier descriptor table by writing a sentinel value at the end. + ; + MACRO + END_WRITE_BARRIERS + + ASSERT __defining_write_barriers +__defining_write_barriers SETL {false} + + ; Switch to the descriptor table section. + WRITEBARRIERAREA + + ; Write the sentinel value to the end of the descriptor table (a function entrypoint address of zero). + DCD 0 + + ; Switch back to the code section. + TEXTAREA + MEND + + ; WRITE_BARRIER_ENTRY + ; + ; Declare the start of a write barrier function. Use similarly to NESTED_ENTRY. This is the only legal way + ; to declare a write barrier function. + ; + MACRO + WRITE_BARRIER_ENTRY $name + + ; Ensure we're called inside a BEGIN_WRITE_BARRIERS section. + ASSERT __defining_write_barriers + + ; Do the standard function declaration logic. Must use a NESTED_ENTRY since we require unwind info to + ; be registered (for the case where the barrier AVs and the runtime needs to recover). + LEAF_ENTRY $name + + ; Record the function name as it's used as the basis for unique label name creation in some of the + ; macros below. + GBLS __write_barrier_name +__write_barrier_name SETS "$name" + + ; Declare globals to collect the values of the offsets of instructions that load GC global values. + GBLA __g_lowest_address_offset + GBLA __g_highest_address_offset + GBLA __g_ephemeral_low_offset + GBLA __g_ephemeral_high_offset + GBLA __g_card_table_offset + + ; Initialize the above offsets to 0xffff. The default of zero is unsatisfactory because we could + ; legally have an offset of zero and we need some way to distinguish unset values (both for debugging + ; and because some write barriers don't use all the globals). +__g_lowest_address_offset SETA 0xffff +__g_highest_address_offset SETA 0xffff +__g_ephemeral_low_offset SETA 0xffff +__g_ephemeral_high_offset SETA 0xffff +__g_card_table_offset SETA 0xffff + + MEND + + ; WRITE_BARRIER_END + ; + ; The partner to WRITE_BARRIER_ENTRY, used like NESTED_END. + ; + MACRO + WRITE_BARRIER_END + + LTORG ; force the literal pool to be emitted here so that copy code picks it up + ; Use the standard macro to end the function definition. + LEAF_END_MARKED $__write_barrier_name + +; Define a local string to hold the name of a label identifying the end of the write barrier function. + LCLS __EndLabelName +__EndLabelName SETS "$__write_barrier_name":CC:"_End" + + ; Switch to the descriptor table section. + WRITEBARRIERAREA + + ; Emit the descripter for this write barrier. The order of these datums must be kept in sync with the + ; definition of the WriteBarrierDescriptor structure in vm\arm\stubs.cpp. + DCD $__write_barrier_name + DCD $__EndLabelName + DCD __g_lowest_address_offset + DCD __g_highest_address_offset + DCD __g_ephemeral_low_offset + DCD __g_ephemeral_high_offset + DCD __g_card_table_offset + + ; Switch back to the code section. + TEXTAREA + + MEND + + ; LOAD_GC_GLOBAL + ; + ; Used any time we want to load the value of one of the supported GC globals into a register. This records + ; the offset of the instructions used to do this (a movw/movt pair) so we can modify the actual value + ; loaded at runtime. + ; + ; Note that a given write barrier can only load a given global once (which will be compile-time asserted + ; below). + ; + MACRO + LOAD_GC_GLOBAL $regName, $globalName + + ; Map the GC global name to the name of the variable tracking the offset for this function. + LCLS __offset_name +__offset_name SETS "__$globalName._offset" + + ; Ensure that we only attempt to load this global at most once in the current barrier function (we + ; have this limitation purely because we only record one offset for each GC global). + ASSERT $__offset_name == 0xffff + + ; Define a unique name for a label we're about to define used in the calculation of the current + ; function offset. + LCLS __offset_label_name +__offset_label_name SETS "$__write_barrier_name$__offset_name" + + ; Define the label. +$__offset_label_name + + ; Write the current function offset into the tracking variable. +$__offset_name SETA ($__offset_label_name - $__FuncStartLabel) + + ; Emit the instructions which will be patched to provide the value of the GC global (we start with a + ; value of zero, so the write barriers have to be patched at least once before first use). + movw $regName, #0 + movt $regName, #0 + MEND + +; +; Now define the macros used in the bodies of write barrier implementations. +; + + ; UPDATE_GC_SHADOW + ; + ; Update the GC shadow heap to aid debugging (no-op unless WRITE_BARRIER_CHECK is defined). Assumes the + ; location being written lies on the GC heap (either we've already performed the dynamic check or this is + ; statically asserted by the JIT by calling the unchecked version of the write barrier). + ; + ; Input: + ; $ptrReg : register containing the location (in the real heap) to be updated + ; $valReg : register containing the value (an objref) to be written to the location above + ; + ; Output: + ; $__wbscratch : trashed + ; + MACRO + UPDATE_GC_SHADOW $ptrReg, $valReg +#ifdef WRITE_BARRIER_CHECK + + ; Need one additional temporary register to hold the shadow pointer. Assume r7 is OK for now (and + ; assert it). If this becomes a problem in the future the register choice can be parameterized. + LCLS pShadow +pShadow SETS "r7" + ASSERT "$ptrReg" != "$pShadow" + ASSERT "$valReg" != "$pShadow" + + push {$pShadow} + + ; Compute address of shadow heap location: + ; pShadow = g_GCShadow + ($ptrReg - g_lowest_address) + ldr $__wbscratch, =g_lowest_address + ldr $__wbscratch, [$__wbscratch] + sub $pShadow, $ptrReg, $__wbscratch + ldr $__wbscratch, =$g_GCShadow + ldr $__wbscratch, [$__wbscratch] + add $pShadow, $__wbscratch + + ; if (pShadow >= g_GCShadow) goto end + ldr $__wbscratch, =$g_GCShadowEnd + ldr $__wbscratch, [$__wbscratch] + cmp $pShadow, $__wbscratch + bhs %FT0 + + ; *pShadow = $valReg + str $valReg, [$pShadow] + + ; Ensure that the write to the shadow heap occurs before the read from the GC heap so that race + ; conditions are caught by INVALIDGCVALUE. + dmb + + ; if (*$ptrReg == $valReg) goto end + ldr $__wbscratch, [$ptrReg] + cmp $__wbscratch, $valReg + beq %FT0 + + ; *pShadow = INVALIDGCVALUE (0xcccccccd) + movw $__wbscratch, #0xcccd + movt $__wbscratch, #0xcccc + str $__wbscratch, [$pShadow] + +0 + pop {$pShadow} +#endif // WRITE_BARRIER_CHECK + MEND + + ; UPDATE_CARD_TABLE + ; + ; Update the card table as necessary (if the object reference being assigned in the barrier refers to an + ; object in the ephemeral generation). Otherwise this macro is a no-op. Assumes the location being written + ; lies on the GC heap (either we've already performed the dynamic check or this is statically asserted by + ; the JIT by calling the unchecked version of the write barrier). + ; + ; Additionally this macro can produce a uni-proc or multi-proc variant of the code. This governs whether + ; we bother to check if the card table has been updated before making our own update (on an MP system it + ; can be helpful to perform this check to avoid cache line thrashing, on an SP system the code path length + ; is more important). + ; + ; Input: + ; $ptrReg : register containing the location to be updated + ; $valReg : register containing the value (an objref) to be written to the location above + ; $mp : boolean indicating whether the code will run on an MP system + ; $tmpReg : additional register that can be trashed (can alias $ptrReg or $valReg if needed) + ; + ; Output: + ; $tmpReg : trashed (defaults to $ptrReg) + ; $__wbscratch : trashed + ; + MACRO + UPDATE_CARD_TABLE $ptrReg, $valReg, $mp, $postGrow, $tmpReg + ASSERT "$ptrReg" != "$__wbscratch" + ASSERT "$valReg" != "$__wbscratch" + ASSERT "$tmpReg" != "$__wbscratch" + + ; In most cases the callers of this macro are fine with scratching $ptrReg, the exception being the + ; ref write barrier, which wants to scratch $valReg instead. Ideally we could set $ptrReg as the + ; default for the $tmpReg parameter, but limitations in armasm won't allow that. Similarly it doesn't + ; seem to like us trying to redefine $tmpReg in the body of the macro. Instead we define a new local + ; string variable and set that either with the value of $tmpReg or $ptrReg if $tmpReg wasn't + ; specified. + LCLS tempReg + IF "$tmpReg" == "" +tempReg SETS "$ptrReg" + ELSE +tempReg SETS "$tmpReg" + ENDIF + + ; Check whether the value object lies in the ephemeral generations. If not we don't have to update the + ; card table. + LOAD_GC_GLOBAL $__wbscratch, g_ephemeral_low + cmp $valReg, $__wbscratch + blo %FT0 + ; Only in post grow higher generation can be beyond ephemeral segment + IF $postGrow + LOAD_GC_GLOBAL $__wbscratch, g_ephemeral_high + cmp $valReg, $__wbscratch + bhs %FT0 + ENDIF + + ; Update the card table. + LOAD_GC_GLOBAL $__wbscratch, g_card_table + add $__wbscratch, $__wbscratch, $ptrReg, lsr #10 + + ; On MP systems make sure the card hasn't already been set first to avoid thrashing cache lines + ; between CPUs. + ; @ARMTODO: Check that the conditional store doesn't unconditionally gain exclusive access to the + ; cache line anyway. Compare perf with a branch over and verify that omitting the compare on uniproc + ; machines really is a perf win. + IF $mp + ldrb $tempReg, [$__wbscratch] + cmp $tempReg, #0xff + movne $tempReg, #0xff + strbne $tempReg, [$__wbscratch] + ELSE + mov $tempReg, #0xff + strb $tempReg, [$__wbscratch] + ENDIF +0 + MEND + + ; CHECK_GC_HEAP_RANGE + ; + ; Verifies that the given value points into the GC heap range. If so the macro will fall through to the + ; following code. Otherwise (if the value points outside the GC heap) a branch to the supplied label will + ; be made. + ; + ; Input: + ; $ptrReg : register containing the location to be updated + ; $label : label branched to on a range check failure + ; + ; Output: + ; $__wbscratch : trashed + ; + MACRO + CHECK_GC_HEAP_RANGE $ptrReg, $label + ASSERT "$ptrReg" != "$__wbscratch" + + LOAD_GC_GLOBAL $__wbscratch, g_lowest_address + cmp $ptrReg, $__wbscratch + blo $label + LOAD_GC_GLOBAL $__wbscratch, g_highest_address + cmp $ptrReg, $__wbscratch + bhs $label + MEND + +; +; Finally define the write barrier functions themselves. Currently we don't provide variations that use +; different input registers. If the JIT wants this at a later stage in order to improve code quality it would +; be a relatively simply change to implement via an additional macro parameter to WRITE_BARRIER_ENTRY. +; +; The calling convention for the first batch of write barriers is: +; +; On entry: +; r0 : the destination address (LHS of the assignment) +; r1 : the object reference (RHS of the assignment) +; +; On exit: +; r0 : trashed +; $__wbscratch : trashed +; + + ; If you update any of the writebarrier be sure to update the sizes of patchable + ; writebarriers in + ; see ValidateWriteBarriers() + + ; The write barriers are macro taking arguments like + ; $name: Name of the write barrier + ; $mp: {true} for multi-proc, {false} otherwise + ; $post: {true} for post-grow version, {false} otherwise + + MACRO + JIT_WRITEBARRIER $name, $mp, $post + WRITE_BARRIER_ENTRY $name + IF $mp + dmb ; Perform a memory barrier + ENDIF + str r1, [r0] ; Write the reference + UPDATE_GC_SHADOW r0, r1 ; Update the shadow GC heap for debugging + UPDATE_CARD_TABLE r0, r1, $mp, $post ; Update the card table if necessary + bx lr + WRITE_BARRIER_END + MEND + + MACRO + JIT_CHECKEDWRITEBARRIER_SP $name, $post + WRITE_BARRIER_ENTRY $name + str r1, [r0] ; Write the reference + CHECK_GC_HEAP_RANGE r0, %F1 ; Check whether the destination is in the GC heap + UPDATE_GC_SHADOW r0, r1 ; Update the shadow GC heap for debugging + UPDATE_CARD_TABLE r0, r1, {false}, $post; Update the card table if necessary +1 + bx lr + WRITE_BARRIER_END + MEND + + MACRO + JIT_CHECKEDWRITEBARRIER_MP $name, $post + WRITE_BARRIER_ENTRY $name + CHECK_GC_HEAP_RANGE r0, %F1 ; Check whether the destination is in the GC heap + dmb ; Perform a memory barrier + str r1, [r0] ; Write the reference + UPDATE_GC_SHADOW r0, r1 ; Update the shadow GC heap for debugging + UPDATE_CARD_TABLE r0, r1, {true}, $post ; Update the card table if necessary + bx lr +1 + str r1, [r0] ; Write the reference + bx lr + WRITE_BARRIER_END + MEND + +; The ByRef write barriers have a slightly different interface: +; +; On entry: +; r0 : the destination address (object reference written here) +; r1 : the source address (points to object reference to write) +; +; On exit: +; r0 : incremented by 4 +; r1 : incremented by 4 +; r2 : trashed +; $__wbscratch : trashed +; + MACRO + JIT_BYREFWRITEBARRIER $name, $mp, $post + WRITE_BARRIER_ENTRY $name + IF $mp + dmb ; Perform a memory barrier + ENDIF + ldr r2, [r1] ; Load target object ref from source pointer + str r2, [r0] ; Write the reference to the destination pointer + CHECK_GC_HEAP_RANGE r0, %F1 ; Check whether the destination is in the GC heap + UPDATE_GC_SHADOW r0, r2 ; Update the shadow GC heap for debugging + UPDATE_CARD_TABLE r0, r2, $mp, $post, r2 ; Update the card table if necessary (trash r2 rather than r0) +1 + add r0, #4 ; Increment the destination pointer by 4 + add r1, #4 ; Increment the source pointer by 4 + bx lr + WRITE_BARRIER_END + MEND + + BEGIN_WRITE_BARRIERS + + ; There 4 versions of each write barriers. A 2x2 combination of multi-proc/single-proc and pre/post grow version + JIT_WRITEBARRIER JIT_WriteBarrier_SP_Pre, {false}, {false} + JIT_WRITEBARRIER JIT_WriteBarrier_SP_Post, {false}, {true} + JIT_WRITEBARRIER JIT_WriteBarrier_MP_Pre, {true}, {false} + JIT_WRITEBARRIER JIT_WriteBarrier_MP_Post, {true}, {true} + + JIT_CHECKEDWRITEBARRIER_SP JIT_CheckedWriteBarrier_SP_Pre, {false} + JIT_CHECKEDWRITEBARRIER_SP JIT_CheckedWriteBarrier_SP_Post, {true} + JIT_CHECKEDWRITEBARRIER_MP JIT_CheckedWriteBarrier_MP_Pre, {false} + JIT_CHECKEDWRITEBARRIER_MP JIT_CheckedWriteBarrier_MP_Post, {true} + + JIT_BYREFWRITEBARRIER JIT_ByRefWriteBarrier_SP_Pre, {false}, {false} + JIT_BYREFWRITEBARRIER JIT_ByRefWriteBarrier_SP_Post, {false}, {true} + JIT_BYREFWRITEBARRIER JIT_ByRefWriteBarrier_MP_Pre, {true}, {false} + JIT_BYREFWRITEBARRIER JIT_ByRefWriteBarrier_MP_Post, {true}, {true} + + END_WRITE_BARRIERS + +#ifdef FEATURE_READYTORUN + + NESTED_ENTRY DelayLoad_MethodCall_FakeProlog + + ; Match what the lazy thunk has pushed. The actual method arguments will be spilled later. + PROLOG_PUSH {r1-r3} + + ; This is where execution really starts. +DelayLoad_MethodCall + EXPORT DelayLoad_MethodCall + + PROLOG_PUSH {r0} + + PROLOG_WITH_TRANSITION_BLOCK 0x0, {true}, DoNotPushArgRegs + + ; Load the helper arguments + ldr r5, [sp,#(__PWTB_TransitionBlock+10*4)] ; pModule + ldr r6, [sp,#(__PWTB_TransitionBlock+11*4)] ; sectionIndex + ldr r7, [sp,#(__PWTB_TransitionBlock+12*4)] ; indirection + + ; Spill the actual method arguments + str r1, [sp,#(__PWTB_TransitionBlock+10*4)] + str r2, [sp,#(__PWTB_TransitionBlock+11*4)] + str r3, [sp,#(__PWTB_TransitionBlock+12*4)] + + add r0, sp, #__PWTB_TransitionBlock ; pTransitionBlock + + mov r1, r7 ; pIndirection + mov r2, r6 ; sectionIndex + mov r3, r5 ; pModule + + bl ExternalMethodFixupWorker + + ; mov the address we patched to in R12 so that we can tail call to it + mov r12, r0 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + + ; Share the patch label + EPILOG_BRANCH ExternalMethodFixupPatchLabel + + NESTED_END + + + MACRO + DynamicHelper $frameFlags, $suffix + + GBLS __FakePrologName +__FakePrologName SETS "DelayLoad_Helper":CC:"$suffix":CC:"_FakeProlog" + + NESTED_ENTRY $__FakePrologName + + ; Match what the lazy thunk has pushed. The actual method arguments will be spilled later. + PROLOG_PUSH {r1-r3} + + GBLS __RealName +__RealName SETS "DelayLoad_Helper":CC:"$suffix" + + ; This is where execution really starts. +$__RealName + EXPORT $__RealName + + PROLOG_PUSH {r0} + + PROLOG_WITH_TRANSITION_BLOCK 0x4, {false}, DoNotPushArgRegs + + ; Load the helper arguments + ldr r5, [sp,#(__PWTB_TransitionBlock+10*4)] ; pModule + ldr r6, [sp,#(__PWTB_TransitionBlock+11*4)] ; sectionIndex + ldr r7, [sp,#(__PWTB_TransitionBlock+12*4)] ; indirection + + ; Spill the actual method arguments + str r1, [sp,#(__PWTB_TransitionBlock+10*4)] + str r2, [sp,#(__PWTB_TransitionBlock+11*4)] + str r3, [sp,#(__PWTB_TransitionBlock+12*4)] + + add r0, sp, #__PWTB_TransitionBlock ; pTransitionBlock + + mov r1, r7 ; pIndirection + mov r2, r6 ; sectionIndex + mov r3, r5 ; pModule + + mov r4, $frameFlags + str r4, [sp,#0] + + bl DynamicHelperWorker + + cbnz r0, %FT0 + ldr r0, [sp,#(__PWTB_TransitionBlock+9*4)] ; The result is stored in the argument area of the transition block + + EPILOG_WITH_TRANSITION_BLOCK_RETURN + +0 + mov r12, r0 + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + EPILOG_BRANCH_REG r12 + + NESTED_END + + MEND + + DynamicHelper DynamicHelperFrameFlags_Default + DynamicHelper DynamicHelperFrameFlags_ObjectArg, _Obj + DynamicHelper DynamicHelperFrameFlags_ObjectArg | DynamicHelperFrameFlags_ObjectArg2, _ObjObj + +#endif // FEATURE_READYTORUN + +; Must be at very end of file + END diff --git a/src/vm/arm/asmmacros.h b/src/vm/arm/asmmacros.h new file mode 100644 index 0000000000..cff79c259b --- /dev/null +++ b/src/vm/arm/asmmacros.h @@ -0,0 +1,161 @@ +// 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. + +;; ==++== +;; + +;; +;; ==--== + +;----------------------------------------------------------------------------- +; Macro used to assign an alternate name to a symbol containing characters normally disallowed in a symbol +; name (e.g. C++ decorated names). + MACRO + SETALIAS $name, $symbol + GBLS $name +$name SETS "|$symbol|" + MEND + + +;----------------------------------------------------------------------------- +; Macro used to end a function with explicit _End label + MACRO + LEAF_END_MARKED $FuncName + + LCLS __EndLabelName +__EndLabelName SETS "$FuncName":CC:"_End" + EXPORT $__EndLabelName +$__EndLabelName + + LEAF_END $FuncName + + MEND + +;----------------------------------------------------------------------------- +; Macro use for enabling C++ to know where to patch code at runtime. + MACRO + PATCH_LABEL $FuncName +$FuncName + EXPORT $FuncName + + MEND + +;----------------------------------------------------------------------------- +; Macro used to check (in debug builds only) whether the stack is 64-bit aligned (a requirement before calling +; out into C++/OS code). Invoke this directly after your prolog (if the stack frame size is fixed) or directly +; before a call (if you have a frame pointer and a dynamic stack). A breakpoint will be invoked if the stack +; is misaligned. +; + MACRO + CHECK_STACK_ALIGNMENT + +#ifdef _DEBUG + push {r0} + add r0, sp, #4 + tst r0, #7 + pop {r0} + beq %F0 + EMIT_BREAKPOINT +0 +#endif + MEND + +;----------------------------------------------------------------------------- +; The following group of macros assist in implementing prologs and epilogs for methods that set up some +; subclass of TransitionFrame. They ensure that the SP is 64-bit aligned at the conclusion of the prolog and +; provide a helper macro to locate the start of the NegInfo (if there is one) for the frame. + +;----------------------------------------------------------------------------- +; Define the prolog for a TransitionFrame-based method. This macro should be called first in the method and +; comprises the entire prolog (i.e. don't modify SP after calling this). Takes the size of the frame's NegInfo +; (which may be zero) and the frame itself. No initialization of the frame is done beyond callee saved +; registers and (non-floating point) argument registers. +; + MACRO + PROLOG_WITH_TRANSITION_BLOCK $extraLocals, $SaveFPArgs, $PushArgRegs + + GBLA __PWTB_FloatArgumentRegisters + GBLA __PWTB_StackAlloc + GBLA __PWTB_TransitionBlock + GBLL __PWTB_SaveFPArgs + + IF "$SaveFPArgs" != "" +__PWTB_SaveFPArgs SETL $SaveFPArgs + ELSE +__PWTB_SaveFPArgs SETL {true} + ENDIF + + IF "$extraLocals" != "" +__PWTB_FloatArgumentRegisters SETA $extraLocals + ELSE +__PWTB_FloatArgumentRegisters SETA 0 + ENDIF + + IF __PWTB_SaveFPArgs + + IF __PWTB_FloatArgumentRegisters:MOD:8 != 0 +__PWTB_FloatArgumentRegisters SETA __PWTB_FloatArgumentRegisters + 4 + ENDIF +__PWTB_TransitionBlock SETA __PWTB_FloatArgumentRegisters + (SIZEOF__FloatArgumentRegisters + 4) ; padding + + ELSE + + IF __PWTB_FloatArgumentRegisters:MOD:8 == 0 +__PWTB_FloatArgumentRegisters SETA __PWTB_FloatArgumentRegisters + 4; padding + ENDIF +__PWTB_TransitionBlock SETA __PWTB_FloatArgumentRegisters + + ENDIF + +__PWTB_StackAlloc SETA __PWTB_TransitionBlock + + IF "$PushArgRegs" != "DoNotPushArgRegs" + ; Spill argument registers. + PROLOG_PUSH {r0-r3} + ENDIF + + ; Spill callee saved registers and return address. + PROLOG_PUSH {r4-r11,lr} + + ; Allocate space for the rest of the frame + PROLOG_STACK_ALLOC __PWTB_StackAlloc + + IF __PWTB_SaveFPArgs + add r6, sp, #(__PWTB_FloatArgumentRegisters) + vstm r6, {s0-s15} + ENDIF + + CHECK_STACK_ALIGNMENT + MEND + +;----------------------------------------------------------------------------- +; Provides a matching epilog to PROLOG_WITH_TRANSITION_BLOCK and ends by preparing for tail-calling. +; Since this is a tail call argument registers are restored. +; + MACRO + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + + IF __PWTB_SaveFPArgs + add r6, sp, #(__PWTB_FloatArgumentRegisters) + vldm r6, {s0-s15} + ENDIF + + EPILOG_STACK_FREE __PWTB_StackAlloc + EPILOG_POP {r4-r11,lr} + EPILOG_POP {r0-r3} + MEND + +;----------------------------------------------------------------------------- +; Provides a matching epilog to PROLOG_WITH_TRANSITION_FRAME and ends by returning to the original caller. +; Since this is not a tail call argument registers are not restored. +; + MACRO + EPILOG_WITH_TRANSITION_BLOCK_RETURN + + EPILOG_STACK_FREE __PWTB_StackAlloc + EPILOG_POP {r4-r11,lr} + EPILOG_STACK_FREE 16 + EPILOG_RETURN + MEND + diff --git a/src/vm/arm/cgencpu.h b/src/vm/arm/cgencpu.h new file mode 100644 index 0000000000..936fdabafb --- /dev/null +++ b/src/vm/arm/cgencpu.h @@ -0,0 +1,1338 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +// + + +#ifndef _TARGET_ARM_ +#error Should only include "cGenCpu.h" for ARM builds +#endif + +#ifndef __cgencpu_h__ +#define __cgencpu_h__ + +#include "utilcode.h" +#include "tls.h" + +// preferred alignment for data +#define DATA_ALIGNMENT 4 + +#define DISPATCH_STUB_FIRST_WORD 0xf8d0 +#define RESOLVE_STUB_FIRST_WORD 0xf8d0 + +class MethodDesc; +class FramedMethodFrame; +class Module; +struct DeclActionInfo; +class ComCallMethodDesc; +class BaseDomain; +class ZapNode; +struct ArgLocDesc; + +#define USE_REDIRECT_FOR_GCSTRESS + +// CPU-dependent functions +Stub * GenerateInitPInvokeFrameHelper(); + +EXTERN_C void checkStack(void); + +#ifdef CROSSGEN_COMPILE +#define GetEEFuncEntryPoint(pfn) 0x1001 +#else +#define GetEEFuncEntryPoint(pfn) GFN_TADDR(pfn) +#endif + +//********************************************************************** + +#define COMMETHOD_PREPAD 12 // # extra bytes to allocate in addition to sizeof(ComCallMethodDesc) +#ifdef FEATURE_COMINTEROP +#define COMMETHOD_CALL_PRESTUB_SIZE 12 +#define COMMETHOD_CALL_PRESTUB_ADDRESS_OFFSET 8 // the offset of the call target address inside the prestub +#endif // FEATURE_COMINTEROP + +#define STACK_ALIGN_SIZE 4 + +#define JUMP_ALLOCATE_SIZE 8 // # bytes to allocate for a jump instruction +#define BACK_TO_BACK_JUMP_ALLOCATE_SIZE 8 // # bytes to allocate for a back to back jump instruction + +//#define HAS_COMPACT_ENTRYPOINTS 1 + +#define HAS_NDIRECT_IMPORT_PRECODE 1 + +#define USE_INDIRECT_CODEHEADER + +#ifdef FEATURE_REMOTING +#define HAS_REMOTING_PRECODE 1 +#endif + +EXTERN_C void getFPReturn(int fpSize, INT64 *pRetVal); +EXTERN_C void setFPReturn(int fpSize, INT64 retVal); + +#define HAS_FIXUP_PRECODE 1 +#define HAS_FIXUP_PRECODE_CHUNKS 1 + +// ThisPtrRetBufPrecode one is necessary for closed delegates over static methods with return buffer +#define HAS_THISPTR_RETBUF_PRECODE 1 + +#define CODE_SIZE_ALIGN 4 +#define CACHE_LINE_SIZE 32 // As per Intel Optimization Manual the cache line size is 32 bytes +#define LOG2SLOT LOG2_PTRSIZE + +#define ENREGISTERED_RETURNTYPE_MAXSIZE 32 // bytes (maximum HFA size is 4 doubles) +#define ENREGISTERED_RETURNTYPE_INTEGER_MAXSIZE 4 // bytes + +#define CALLDESCR_ARGREGS 1 // CallDescrWorker has ArgumentRegister parameter +#define CALLDESCR_FPARGREGS 1 // CallDescrWorker has FloatArgumentRegisters parameter + +// Max size of optimized TLS helpers +#define TLS_GETTER_MAX_SIZE 0x10 + +// Given a return address retrieved during stackwalk, +// this is the offset by which it should be decremented to arrive at the callsite. +#define STACKWALK_CONTROLPC_ADJUST_OFFSET 2 + +//======================================================================= +// IMPORTANT: This value is used to figure out how much to allocate +// for a fixed array of FieldMarshaler's. That means it must be at least +// as large as the largest FieldMarshaler subclass. This requirement +// is guarded by an assert. +//======================================================================= +#define MAXFIELDMARSHALERSIZE 24 + +//********************************************************************** +// Parameter size +//********************************************************************** + +typedef INT32 StackElemType; +#define STACK_ELEM_SIZE sizeof(StackElemType) + +// !! This expression assumes STACK_ELEM_SIZE is a power of 2. +#define StackElemSize(parmSize) (((parmSize) + STACK_ELEM_SIZE - 1) & ~((ULONG)(STACK_ELEM_SIZE - 1))) + +//********************************************************************** +// Frames +//********************************************************************** + +//-------------------------------------------------------------------- +// This represents the callee saved (non-volatile) registers saved as +// of a FramedMethodFrame. +//-------------------------------------------------------------------- +typedef DPTR(struct CalleeSavedRegisters) PTR_CalleeSavedRegisters; +struct CalleeSavedRegisters { + INT32 r4, r5, r6, r7, r8, r9, r10; + INT32 r11; // frame pointer + INT32 r14; // link register +}; + +//-------------------------------------------------------------------- +// This represents the arguments that are stored in volatile registers. +// This should not overlap the CalleeSavedRegisters since those are already +// saved separately and it would be wasteful to save the same register twice. +// If we do use a non-volatile register as an argument, then the ArgIterator +// will probably have to communicate this back to the PromoteCallerStack +// routine to avoid a double promotion. +//-------------------------------------------------------------------- +typedef DPTR(struct ArgumentRegisters) PTR_ArgumentRegisters; +struct ArgumentRegisters { + INT32 r[4]; // r0, r1, r2, r3 +}; +#define NUM_ARGUMENT_REGISTERS 4 + +//-------------------------------------------------------------------- +// This represents the floating point argument registers which are saved +// as part of the NegInfo for a FramedMethodFrame. Note that these +// might not be saved by all stubs: typically only those that call into +// C++ helpers will need to preserve the values in these volatile +// registers. +//-------------------------------------------------------------------- +typedef DPTR(struct FloatArgumentRegisters) PTR_FloatArgumentRegisters; +struct FloatArgumentRegisters { + union + { + float s[16]; // s0-s15 + double d[8]; // d0-d7 + }; +}; + +// forward decl +struct REGDISPLAY; +typedef REGDISPLAY *PREGDISPLAY; + +// Sufficient context for Try/Catch restoration. +struct EHContext { + INT32 r[16]; // note: includes r15(pc) + void Setup(PCODE resumePC, PREGDISPLAY regs); + + inline TADDR GetSP() { + LIMITED_METHOD_CONTRACT; + return (TADDR)r[13]; + } + inline void SetSP(LPVOID esp) { + LIMITED_METHOD_CONTRACT; + r[13] = (INT32)(size_t)esp; + } + + inline LPVOID GetFP() { + LIMITED_METHOD_CONTRACT; + return (LPVOID)(UINT_PTR)r[11]; + } + + inline void SetArg(LPVOID arg) { + LIMITED_METHOD_CONTRACT; + r[0] = (INT32)(size_t)arg; + } +}; + +#define ARGUMENTREGISTERS_SIZE sizeof(ArgumentRegisters) + +//********************************************************************** +// Exception handling +//********************************************************************** + +inline PCODE GetIP(const T_CONTEXT * context) { + LIMITED_METHOD_DAC_CONTRACT; + return PCODE(context->Pc); +} + +inline void SetIP(T_CONTEXT *context, PCODE eip) { + LIMITED_METHOD_DAC_CONTRACT; + context->Pc = DWORD(eip); +} + +inline TADDR GetSP(const T_CONTEXT * context) { + LIMITED_METHOD_DAC_CONTRACT; + return TADDR(context->Sp); +} + +inline PCODE GetLR(const T_CONTEXT * context) { + LIMITED_METHOD_DAC_CONTRACT; + return PCODE(context->Lr); +} + +extern "C" LPVOID __stdcall GetCurrentSP(); + +inline void SetSP(T_CONTEXT *context, TADDR esp) { + LIMITED_METHOD_DAC_CONTRACT; + context->Sp = DWORD(esp); +} + +inline void SetFP(T_CONTEXT *context, TADDR ebp) { + LIMITED_METHOD_DAC_CONTRACT; + context->R11 = DWORD(ebp); +} + +inline TADDR GetFP(const T_CONTEXT * context) +{ + LIMITED_METHOD_DAC_CONTRACT; + return (TADDR)(context->R11); +} + +inline void ClearITState(T_CONTEXT *context) { + LIMITED_METHOD_DAC_CONTRACT; + context->Cpsr = context->Cpsr & 0xf9ff03ff; +} + +#ifdef FEATURE_COMINTEROP +void emitCOMStubCall (ComCallMethodDesc *pCOMMethod, PCODE target); +#endif // FEATURE_COMINTEROP + +//------------------------------------------------------------------------ +inline void emitJump(LPBYTE pBuffer, LPVOID target) +{ + LIMITED_METHOD_CONTRACT; + + // The PC-relative load we emit below requires 4-byte alignment for the offset to be calculated correctly. + _ASSERTE(((UINT_PTR)pBuffer & 3) == 0); + + DWORD * pCode = (DWORD *)pBuffer; + + // ldr pc, [pc, #0] + pCode[0] = 0xf000f8df; + pCode[1] = (DWORD)target; +} + +//------------------------------------------------------------------------ +// Given the same pBuffer that was used by emitJump this method +// decodes the instructions and returns the jump target +inline PCODE decodeJump(PCODE pCode) +{ + LIMITED_METHOD_CONTRACT; + + TADDR pInstr = PCODEToPINSTR(pCode); + + return *dac_cast(pInstr + sizeof(DWORD)); +} + +// +// On IA64 back to back jumps should be separated by a nop bundle to get +// the best performance from the hardware's branch prediction logic. +// For all other platforms back to back jumps don't require anything special +// That is why we have these two wrapper functions that call emitJump and decodeJump +// + +//------------------------------------------------------------------------ +inline BOOL isJump(PCODE pCode) +{ + LIMITED_METHOD_DAC_CONTRACT; + + TADDR pInstr = PCODEToPINSTR(pCode); + + return *dac_cast(pInstr) == 0xf000f8df; +} + +//------------------------------------------------------------------------ +inline BOOL isBackToBackJump(PCODE pBuffer) +{ + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + return isJump(pBuffer); +} + +//------------------------------------------------------------------------ +inline void emitBackToBackJump(LPBYTE pBuffer, LPVOID target) +{ + WRAPPER_NO_CONTRACT; + emitJump(pBuffer, target); +} + +//------------------------------------------------------------------------ +inline PCODE decodeBackToBackJump(PCODE pBuffer) +{ + WRAPPER_NO_CONTRACT; + return decodeJump(pBuffer); +} + +//---------------------------------------------------------------------- +#include "stublink.h" +struct ArrayOpScript; + +#define THUMB_CODE 1 + +inline BOOL IsThumbCode(PCODE pCode) +{ + return (pCode & THUMB_CODE) != 0; +} + +struct ThumbReg +{ + int reg; + ThumbReg(int reg):reg(reg) + { + _ASSERTE(0 <= reg && reg < 16); + } + + operator int () + { + return reg; + } + + int operator == (ThumbReg other) + { + return reg == other.reg; + } + + int operator != (ThumbReg other) + { + return reg != other.reg; + } + + WORD Mask() const + { + return 1 << reg; + } + +}; + +struct ThumbCond +{ + int cond; + ThumbCond(int cond):cond(cond) + { + _ASSERTE(0 <= cond && cond < 16); + } +}; + +struct ThumbVFPSingleReg +{ + int reg; + ThumbVFPSingleReg(int reg):reg(reg) + { + _ASSERTE(0 <= reg && reg < 31); + } + + operator int () + { + return reg; + } + + int operator == (ThumbVFPSingleReg other) + { + return reg == other.reg; + } + + int operator != (ThumbVFPSingleReg other) + { + return reg != other.reg; + } + + WORD Mask() const + { + return 1 << reg; + } + +}; + +struct ThumbVFPDoubleReg +{ + int reg; + ThumbVFPDoubleReg(int reg):reg(reg) + { + _ASSERTE(0 <= reg && reg < 31); + } + + operator int () + { + return reg; + } + + int operator == (ThumbVFPDoubleReg other) + { + return reg == other.reg; + } + + int operator != (ThumbVFPDoubleReg other) + { + return reg != other.reg; + } + + WORD Mask() const + { + return 1 << reg; + } +}; + +const ThumbReg thumbRegFp = ThumbReg(11); +const ThumbReg thumbRegSp = ThumbReg(13); +const ThumbReg thumbRegLr = ThumbReg(14); +const ThumbReg thumbRegPc = ThumbReg(15); + +const ThumbCond thumbCondEq = ThumbCond(0); +const ThumbCond thumbCondNe = ThumbCond(1); +const ThumbCond thumbCondCs = ThumbCond(2); +const ThumbCond thumbCondCc = ThumbCond(3); +const ThumbCond thumbCondMi = ThumbCond(4); +const ThumbCond thumbCondPl = ThumbCond(5); +const ThumbCond thumbCondVs = ThumbCond(6); +const ThumbCond thumbCondVc = ThumbCond(7); +const ThumbCond thumbCondHi = ThumbCond(8); +const ThumbCond thumbCondLs = ThumbCond(9); +const ThumbCond thumbCondGe = ThumbCond(10); +const ThumbCond thumbCondLt = ThumbCond(11); +const ThumbCond thumbCondGt = ThumbCond(12); +const ThumbCond thumbCondLe = ThumbCond(13); +const ThumbCond thumbCondAl = ThumbCond(14); + +class StubLinkerCPU : public StubLinker +{ +public: + static void Init(); + + void ThumbEmitProlog(UINT cCalleeSavedRegs, UINT cbStackFrame, BOOL fPushArgRegs) + { + _ASSERTE(!m_fProlog); + + // Record the parameters of this prolog so that we can generate a matching epilog and unwind info. + DescribeProlog(cCalleeSavedRegs, cbStackFrame, fPushArgRegs); + + // Trivial prologs (which is all that we support initially) consist of between one and three + // instructions. + + // 1) Push argument registers. This is all or nothing (if we push, we push R0-R3). + if (fPushArgRegs) + { + // push {r0-r3} + ThumbEmitPush(ThumbReg(0).Mask() | ThumbReg(1).Mask() | ThumbReg(2).Mask() | ThumbReg(3).Mask()); + } + + // 2) Push callee saved registers. We always start pushing at R4, and only saved consecutive registers + // from there (max is R11). Additionally we always assume LR is saved for these types of prolog. + // push {r4-rX,lr} + WORD wRegisters = thumbRegLr.Mask(); + for (unsigned int i = 4; i < (4 + cCalleeSavedRegs); i++) + wRegisters |= ThumbReg(i).Mask(); + ThumbEmitPush(wRegisters); + + // 3) Reserve space on the stack for the rest of the frame. + if (cbStackFrame) + { + // sub sp, #cbStackFrame + ThumbEmitSubSp(cbStackFrame); + } + } + + void ThumbEmitEpilog() + { + // Generate an epilog matching a prolog generated by ThumbEmitProlog. + _ASSERTE(m_fProlog); + + // If additional stack space for a frame was allocated remove it now. + if (m_cbStackFrame) + { + // add sp, #m_cbStackFrame + ThumbEmitAddSp(m_cbStackFrame); + } + + // Pop callee saved registers (we always have at least LR). If no argument registers were saved then + // we can restore LR back into PC and we're done. Otherwise LR needs to be restored into LR. + // pop {r4-rX,lr|pc} + WORD wRegisters = m_fPushArgRegs ? thumbRegLr.Mask() : thumbRegPc.Mask(); + for (unsigned int i = 4; i < (4 + m_cCalleeSavedRegs); i++) + wRegisters |= ThumbReg(i).Mask(); + ThumbEmitPop(wRegisters); + + if (!m_fPushArgRegs) + return; + + // We pushed the argument registers. These aren't restored, but we need to reclaim the stack space. + // add sp, #16 + ThumbEmitAddSp(16); + + // Return. The return address has been restored into LR at this point. + // bx lr + ThumbEmitJumpRegister(thumbRegLr); + } + + void ThumbEmitGetThread(TLSACCESSMODE mode, ThumbReg dest); + + void ThumbEmitNop() + { + // nop + Emit16(0xbf00); + } + + void ThumbEmitBreakpoint() + { + // Permanently undefined instruction #0xfe (see ARMv7-A A6.2.6). The debugger seems to accept this as + // a reasonable breakpoint substitute (it's what DebugBreak uses). Bkpt #0, on the other hand, always + // seems to flow directly to the kernel debugger (even if we ignore it there it doesn't seem to be + // picked up by the user mode debugger). + Emit16(0xdefe); + } + + void ThumbEmitMovConstant(ThumbReg dest, int constant) + { + _ASSERT(dest != thumbRegPc); + + //Emit 2 Byte instructions when dest reg < 8 & constant <256 + if(dest <= 7 && constant < 256 && constant >= 0) + { + Emit16((WORD)(0x2000 | dest<<8 | (WORD)constant)); + } + else // emit 4 byte instructions + { + WORD wConstantLow = (WORD)(constant & 0xffff); + WORD wConstantHigh = (WORD)(constant >> 16); + + // movw regDest, #wConstantLow + Emit16((WORD)(0xf240 | (wConstantLow >> 12) | ((wConstantLow & 0x0800) ? 0x0400 : 0x0000))); + Emit16((WORD)((dest << 8) | (((wConstantLow >> 8) & 0x0007) << 12) | (wConstantLow & 0x00ff))); + + if (wConstantHigh) + { + // movt regDest, #wConstantHighw + Emit16((WORD)(0xf2c0 | (wConstantHigh >> 12) | ((wConstantHigh & 0x0800) ? 0x0400 : 0x0000))); + Emit16((WORD)((dest << 8) | (((wConstantHigh >> 8) & 0x0007) << 12) | (wConstantHigh & 0x00ff))); + } + } + } + + void ThumbEmitLoadRegIndirect(ThumbReg dest, ThumbReg source, int offset) + { + _ASSERTE((offset >= 0) && (offset <= 4095)); + + // ldr regDest, [regSource + #offset] + if ((dest < 8) && (source < 8) && ((offset & 0x3) == 0) && (offset < 125)) + { + // Encoding T1 + Emit16((WORD)(0x6800 | ((offset >> 2) << 6) | (source << 3) | dest)); + } + else + { + // Encoding T3 + Emit16((WORD)(0xf8d0 | source)); + Emit16((WORD)((dest << 12) | offset)); + } + } + + void ThumbEmitLoadIndirectPostIncrement(ThumbReg dest, ThumbReg source, int offset) + { + _ASSERTE((offset >= 0) && (offset <= 255)); + + // ldr regDest, [regSource], #offset + Emit16((WORD)(0xf850 | source)); + Emit16((WORD)(0x0b00 | (dest << 12) | offset)); + } + + void ThumbEmitStoreRegIndirect(ThumbReg source, ThumbReg dest, int offset) + { + _ASSERTE((offset >= -255) && (offset <= 4095)); + + // str regSource, [regDest + #offset] + if (offset < 0) + { + Emit16((WORD)(0xf840 | dest)); + Emit16((WORD)(0x0C00 | (source << 12) | (UINT8)(-offset))); + } + else + if ((dest < 8) && (source < 8) && ((offset & 0x3) == 0) && (offset < 125)) + { + // Encoding T1 + Emit16((WORD)(0x6000 | ((offset >> 2) << 6) | (dest << 3) | source)); + } + else + { + // Encoding T3 + Emit16((WORD)(0xf8c0 | dest)); + Emit16((WORD)((source << 12) | offset)); + } + } + + void ThumbEmitStoreIndirectPostIncrement(ThumbReg source, ThumbReg dest, int offset) + { + _ASSERTE((offset >= 0) && (offset <= 255)); + + // str regSource, [regDest], #offset + Emit16((WORD)(0xf840 | dest)); + Emit16((WORD)(0x0b00 | (source << 12) | offset)); + } + + void ThumbEmitLoadOffsetScaledReg(ThumbReg dest, ThumbReg base, ThumbReg offset, int shift) + { + _ASSERTE(shift >=0 && shift <=3); + + Emit16((WORD)(0xf850 | base)); + Emit16((WORD)((dest << 12) | (shift << 4) | offset)); + } + + void ThumbEmitCallRegister(ThumbReg target) + { + // blx regTarget + Emit16((WORD)(0x4780 | (target << 3))); + } + + void ThumbEmitJumpRegister(ThumbReg target) + { + // bx regTarget + Emit16((WORD)(0x4700 | (target << 3))); + } + + void ThumbEmitMovRegReg(ThumbReg dest, ThumbReg source) + { + // mov regDest, regSource + Emit16((WORD)(0x4600 | ((dest > 7) ? 0x0080 : 0x0000) | (source << 3) | (dest & 0x0007))); + } + + //Assuming SP is only subtracted in prolog + void ThumbEmitSubSp(int value) + { + _ASSERTE(value >= 0); + _ASSERTE((value & 0x3) == 0); + + if(value < 512) + { + // encoding T1 + // sub sp, sp, #(value >> 2) + Emit16((WORD)(0xb080 | (value >> 2))); + } + else if(value < 4096) + { + // Using 32-bit encoding + Emit16((WORD)(0xf2ad| ((value & 0x0800) >> 1))); + Emit16((WORD)(0x0d00| ((value & 0x0700) << 4) | (value & 0x00ff))); + } + else + { + // For values >= 4K (pageSize) must check for guard page + +#ifndef CROSSGEN_COMPILE + // mov r4, value + ThumbEmitMovConstant(ThumbReg(4), value); + // mov r12, checkStack + ThumbEmitMovConstant(ThumbReg(12), (int)checkStack); + // bl r12 + ThumbEmitCallRegister(ThumbReg(12)); +#endif + + // sub sp,sp,r4 + Emit16((WORD)0xebad); + Emit16((WORD)0x0d04); + } + } + + void ThumbEmitAddSp(int value) + { + _ASSERTE(value >= 0); + _ASSERTE((value & 0x3) == 0); + + if(value < 512) + { + // encoding T2 + // add sp, sp, #(value >> 2) + Emit16((WORD)(0xb000 | (value >> 2))); + } + else if(value < 4096) + { + // Using 32-bit encoding T4 + Emit16((WORD)(0xf20d| ((value & 0x0800) >> 1))); + Emit16((WORD)(0x0d00| ((value & 0x0700) << 4) | (value & 0x00ff))); + } + else + { + //Must use temp register for values >=4096 + ThumbEmitMovConstant(ThumbReg(12), value); + // add sp,sp,r12 + Emit16((WORD)0x44e5); + } + } + + void ThumbEmitAddReg(ThumbReg dest, ThumbReg source) + { + + _ASSERTE(dest != source); + Emit16((WORD)(0x4400 | ((dest & 0x8)<<4) | (source<<3) | (dest & 0x7))); + } + + void ThumbEmitAdd(ThumbReg dest, ThumbReg source, unsigned int value) + { + + if(value<4096) + { + // addw dest, source, #value + unsigned int i = (value & 0x800) >> 11; + unsigned int imm3 = (value & 0x700) >> 8; + unsigned int imm8 = value & 0xff; + Emit16((WORD)(0xf200 | (i << 10) | source)); + Emit16((WORD)((imm3 << 12) | (dest << 8) | imm8)); + } + else + { + // if immediate is more than 4096 only ADD (register) will work + // move immediate to dest reg and call ADD(reg) + // this will not work if dest is same as source. + _ASSERTE(dest != source); + ThumbEmitMovConstant(dest, value); + ThumbEmitAddReg(dest, source); + } + } + + void ThumbEmitSub(ThumbReg dest, ThumbReg source, unsigned int value) + { + _ASSERTE(value < 4096); + + // subw dest, source, #value + unsigned int i = (value & 0x800) >> 11; + unsigned int imm3 = (value & 0x700) >> 8; + unsigned int imm8 = value & 0xff; + Emit16((WORD)(0xf2a0 | (i << 10) | source)); + Emit16((WORD)((imm3 << 12) | (dest << 8) | imm8)); + } + + void ThumbEmitCmpReg(ThumbReg reg1, ThumbReg reg2) + { + if(reg1 < 8 && reg2 <8) + { + Emit16((WORD)(0x4280 | reg2 << 3 | reg1)); + } + else + { + _ASSERTE(reg1 != ThumbReg(15) && reg2 != ThumbReg(15)); + Emit16((WORD)(0x4500 | reg2 << 3 | (reg1 & 0x7) | (reg1 & 0x8 ? 0x80 : 0x0))); + } + } + + void ThumbEmitIncrement(ThumbReg dest, unsigned int value) + { + while (value) + { + if (value >= 4095) + { + // addw , , #4095 + ThumbEmitAdd(dest, dest, 4095); + value -= 4095; + } + else if (value <= 255) + { + // add , #value + Emit16((WORD)(0x3000 | (dest << 8) | value)); + break; + } + else + { + // addw , , #value + ThumbEmitAdd(dest, dest, value); + break; + } + } + } + + void ThumbEmitPush(WORD registers) + { + _ASSERTE(registers != 0); + _ASSERTE((registers & 0xa000) == 0); // Pushing SP or PC undefined + + // push {registers} + if (CountBits(registers) == 1) + { + // Encoding T3 (exactly one register, high or low) + WORD reg = 15; + while ((registers & (WORD)(1 << reg)) == 0) + { + reg--; + } + Emit16(0xf84d); + Emit16(0x0d04 | (reg << 12)); + } + else if ((registers & 0xbf00) == 0) + { + // Encoding T1 (low registers plus maybe LR) + Emit16(0xb400 | (registers & thumbRegLr.Mask() ? 0x0100: 0x0000) | (registers & 0x00ff)); + } + else + { + // Encoding T2 (two or more registers, high or low) + Emit16(0xe92d); + Emit16(registers); + } + } + + void ThumbEmitLoadStoreMultiple(ThumbReg base, bool load, WORD registers) + { + _ASSERTE(CountBits(registers) > 1); + _ASSERTE((registers & 0xFF00) == 0); // This only supports the small encoding + _ASSERTE(base < 8); // This only supports the small encoding + _ASSERTE((base.Mask() & registers) == 0); // This only supports the small encoding + + // (LDM|STM) base, {registers} + WORD flag = load ? 0x0800 : 0; + Emit16(0xc000 | flag | ((base & 7) << 8) | (registers & 0xFF)); + } + + void ThumbEmitPop(WORD registers) + { + _ASSERTE(registers != 0); + _ASSERTE((registers & 0xc000) != 0xc000); // Popping PC and LR together undefined + + // pop {registers} + if (CountBits(registers) == 1) + { + // Encoding T3 (exactly one register, high or low) + WORD reg = 15; + while ((registers & (WORD)(1 << reg)) == 0) + { + reg--; + } + Emit16(0xf85d); + Emit16(0x0b04 | (reg << 12)); + } + else if ((registers & 0x7f00) == 0) + { + // Encoding T1 (low registers plus maybe PC) + Emit16(0xbc00 | (registers & thumbRegPc.Mask() ? 0x0100: 0x0000) | (registers & 0x00ff)); + } + else + { + // Encoding T2 (two or more registers, high or low) + Emit16(0xe8bd); + Emit16(registers); + } + } + + void ThumbEmitLoadVFPSingleRegIndirect(ThumbVFPSingleReg dest, ThumbReg source, int offset) + { + _ASSERTE((offset >= -1020) && (offset <= 1020)); + _ASSERTE(offset%4==0); + + Emit16((WORD) (0xed10 | ((offset > 0 ? 0x1: 0x0) << 7) | ((dest & 0x1) << 6) | source)); + Emit16((WORD) (0x0a00 | ((dest & 0x1e) << 11) | (abs(offset)>>2))); + } + + void ThumbEmitLoadVFPDoubleRegIndirect(ThumbVFPDoubleReg dest, ThumbReg source, int offset) + { + _ASSERTE((offset >= -1020) && (offset <= 1020)); + _ASSERTE(offset%4==0); + + Emit16((WORD) (0xed10 | ((offset > 0 ? 0x1: 0x0) << 7) | ((dest & 0x10) << 6) | source)); + Emit16((WORD) (0x0b00 | ((dest & 0xf) << 12) | (abs(offset)>>2))); + } + +#ifdef FEATURE_INTERPRETER + void ThumbEmitStoreMultipleVFPDoubleReg(ThumbVFPDoubleReg source, ThumbReg dest, unsigned numRegs) + { + _ASSERTE((numRegs + source) <= 16); + + // The third nibble is 0x8; the 0x4 bit (D) is zero because the source reg number must be less + // than 16 for double registers. + Emit16((WORD) (0xec80 | 0x80 | dest)); + Emit16((WORD) (((source & 0xf) << 12) | 0xb00 | numRegs)); + } + + void ThumbEmitLoadMultipleVFPDoubleReg(ThumbVFPDoubleReg dest, ThumbReg source, unsigned numRegs) + { + _ASSERTE((numRegs + dest) <= 16); + + // The third nibble is 0x8; the 0x4 bit (D) is zero because the source reg number must be less + // than 16 for double registers. + Emit16((WORD) (0xec90 | 0x80 | source)); + Emit16((WORD) (((dest & 0xf) << 12) | 0xb00 | numRegs)); + } +#endif // FEATURE_INTERPRETER + + void EmitStubLinkFrame(TADDR pFrameVptr, int offsetOfFrame, int offsetOfTransitionBlock); + void EmitStubUnlinkFrame(); + + void ThumbEmitCondFlagJump(CodeLabel * target,UINT cond); + + void ThumbEmitCondRegJump(CodeLabel *target, BOOL nonzero, ThumbReg reg); + + void ThumbEmitNearJump(CodeLabel *target); + + // Scratches r12. + void ThumbEmitCallManagedMethod(MethodDesc *pMD, bool fTailcall); + + void EmitUnboxMethodStub(MethodDesc* pRealMD); + static UINT_PTR HashMulticastInvoke(MetaSig* pSig); + + void EmitMulticastInvoke(UINT_PTR hash); + void EmitSecureDelegateInvoke(UINT_PTR hash); + void EmitShuffleThunk(struct ShuffleEntry *pShuffleEntryArray); +#if defined(FEATURE_SHARE_GENERIC_CODE) + void EmitInstantiatingMethodStub(MethodDesc* pSharedMD, void* extra); +#endif // FEATURE_SHARE_GENERIC_CODE + + static Stub * CreateTailCallCopyArgsThunk(CORINFO_SIG_INFO * pSig, + CorInfoHelperTailCallSpecialHandling flags); + +private: + void ThumbCopyOneTailCallArg(UINT * pnSrcAlign, const ArgLocDesc * pArgLoc, UINT * pcbStackSpace); + void ThumbEmitCallWithGenericInstantiationParameter(MethodDesc *pMD, void *pHiddenArg); +}; + +extern "C" void SinglecastDelegateInvokeStub(); + +// SEH info forward declarations + +inline BOOL IsUnmanagedValueTypeReturnedByRef(UINT sizeofvaluetype) +{ + LIMITED_METHOD_CONTRACT; + + // structure that dont fit in the machine-word size are returned + // by reference. + return (sizeofvaluetype > 4); +} + +struct DECLSPEC_ALIGN(4) UMEntryThunkCode +{ + WORD m_code[4]; + + TADDR m_pTargetCode; + TADDR m_pvSecretParam; + + void Encode(BYTE* pTargetCode, void* pvSecretParam); + + LPCBYTE GetEntryPoint() const + { + LIMITED_METHOD_CONTRACT; + + return (LPCBYTE)((TADDR)this | THUMB_CODE); + } + + static int GetEntryPointOffset() + { + LIMITED_METHOD_CONTRACT; + + return 0; + } +}; + +struct HijackArgs +{ + union + { + DWORD R0; + size_t ReturnValue[1]; // this may not be the return value when return is >32bits + // or return value is in VFP reg but it works for us as + // this is only used by functions OnHijackWorker() + }; + + // + // Non-volatile Integer registers + // + DWORD R4; + DWORD R5; + DWORD R6; + DWORD R7; + DWORD R8; + DWORD R9; + DWORD R10; + DWORD R11; + + union + { + DWORD Lr; + size_t ReturnAddress; + }; +}; + +// ClrFlushInstructionCache is used when we want to call FlushInstructionCache +// for a specific architecture in the common code, but not for other architectures. +// On IA64 ClrFlushInstructionCache calls the Kernel FlushInstructionCache function +// to flush the instruction cache. +// We call ClrFlushInstructionCache whenever we create or modify code in the heap. +// Currently ClrFlushInstructionCache has no effect on X86 +// + +inline BOOL ClrFlushInstructionCache(LPCVOID pCodeAddr, size_t sizeOfCode) +{ +#ifdef CROSSGEN_COMPILE + // The code won't be executed when we are cross-compiling so flush instruction cache is unnecessary + return TRUE; +#else + return FlushInstructionCache(GetCurrentProcess(), pCodeAddr, sizeOfCode); +#endif +} + +#ifndef FEATURE_IMPLICIT_TLS +// +// JIT HELPER ALIASING FOR PORTABILITY. +// +// Create alias for optimized implementations of helpers provided on this platform +// +// optimized static helpers +#define JIT_GetSharedGCStaticBase JIT_GetSharedGCStaticBase_InlineGetAppDomain +#define JIT_GetSharedNonGCStaticBase JIT_GetSharedNonGCStaticBase_InlineGetAppDomain +#define JIT_GetSharedGCStaticBaseNoCtor JIT_GetSharedGCStaticBaseNoCtor_InlineGetAppDomain +#define JIT_GetSharedNonGCStaticBaseNoCtor JIT_GetSharedNonGCStaticBaseNoCtor_InlineGetAppDomain + +#endif + +#ifndef FEATURE_PAL +#define JIT_Stelem_Ref JIT_Stelem_Ref +#endif + +//------------------------------------------------------------------------ +// +// Precode definitions +// +//------------------------------------------------------------------------ +// +// Note: If you introduce new precode implementation below, then please +// update PrecodeStubManager::CheckIsStub_Internal to account for it. + +EXTERN_C VOID STDCALL PrecodeFixupThunk(); + +#define PRECODE_ALIGNMENT CODE_SIZE_ALIGN +#define SIZEOF_PRECODE_BASE CODE_SIZE_ALIGN +#define OFFSETOF_PRECODE_TYPE 0 + +// Invalid precode type +struct InvalidPrecode { + static const int Type = 0; +}; + +struct StubPrecode { + + static const int Type = 0xdf; + + // ldr r12, [pc, #8] ; =m_pMethodDesc + // ldr pc, [pc, #0] ; =m_pTarget + // dcd pTarget + // dcd pMethodDesc + WORD m_rgCode[4]; + TADDR m_pTarget; + TADDR m_pMethodDesc; + + void Init(MethodDesc* pMD, LoaderAllocator *pLoaderAllocator); + + TADDR GetMethodDesc() + { + LIMITED_METHOD_DAC_CONTRACT; + return m_pMethodDesc; + } + + PCODE GetTarget() + { + LIMITED_METHOD_DAC_CONTRACT; + return m_pTarget; + } + + BOOL SetTargetInterlocked(TADDR target, TADDR expected) + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + } + CONTRACTL_END; + + EnsureWritableExecutablePages(&m_pTarget); + return (TADDR)InterlockedCompareExchange( + (LONG*)&m_pTarget, (LONG)target, (LONG)expected) == expected; + } + +#ifdef FEATURE_PREJIT + void Fixup(DataImage *image); +#endif +}; +typedef DPTR(StubPrecode) PTR_StubPrecode; + + +struct NDirectImportPrecode { + + static const int Type = 0xe0; + + // ldr r12, [pc, #4] ; =m_pMethodDesc + // ldr pc, [pc, #4] ; =m_pTarget + // dcd pMethodDesc + // dcd pTarget + WORD m_rgCode[4]; + TADDR m_pMethodDesc; // Notice that the fields are reversed compared to StubPrecode. Precode::GetType + // takes advantage of this to detect NDirectImportPrecode. + TADDR m_pTarget; + + void Init(MethodDesc* pMD, LoaderAllocator *pLoaderAllocator); + + TADDR GetMethodDesc() + { + LIMITED_METHOD_DAC_CONTRACT; + return m_pMethodDesc; + } + + PCODE GetTarget() + { + LIMITED_METHOD_DAC_CONTRACT; + return m_pTarget; + } + + LPVOID GetEntrypoint() + { + LIMITED_METHOD_CONTRACT; + return (LPVOID)(dac_cast(this) + THUMB_CODE); + } + +#ifdef FEATURE_PREJIT + void Fixup(DataImage *image); +#endif +}; +typedef DPTR(NDirectImportPrecode) PTR_NDirectImportPrecode; + + +struct FixupPrecode { + + static const int Type = 0xfc; + + // mov r12, pc + // ldr pc, [pc, #4] ; =m_pTarget + // dcb m_MethodDescChunkIndex + // dcb m_PrecodeChunkIndex + // dcd m_pTarget + WORD m_rgCode[3]; + BYTE m_MethodDescChunkIndex; + BYTE m_PrecodeChunkIndex; + TADDR m_pTarget; + + void Init(MethodDesc* pMD, LoaderAllocator *pLoaderAllocator, int iMethodDescChunkIndex = 0, int iPrecodeChunkIndex = 0); + + TADDR GetBase() + { + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + + return dac_cast(this) + (m_PrecodeChunkIndex + 1) * sizeof(FixupPrecode); + } + + TADDR GetMethodDesc(); + + PCODE GetTarget() + { + LIMITED_METHOD_DAC_CONTRACT; + return m_pTarget; + } + + BOOL SetTargetInterlocked(TADDR target, TADDR expected) + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + } + CONTRACTL_END; + + EnsureWritableExecutablePages(&m_pTarget); + return (TADDR)InterlockedCompareExchange( + (LONG*)&m_pTarget, (LONG)target, (LONG)expected) == expected; + } + + static BOOL IsFixupPrecodeByASM(PCODE addr) + { + PTR_WORD pInstr = dac_cast(PCODEToPINSTR(addr)); + + return + (pInstr[0] == 0x46fc) && + (pInstr[1] == 0xf8df) && + (pInstr[2] == 0xf004); + } + +#ifdef FEATURE_PREJIT + // Partial initialization. Used to save regrouped chunks. + void InitForSave(int iPrecodeChunkIndex); + + void Fixup(DataImage *image, MethodDesc * pMD); +#endif + +#ifdef DACCESS_COMPILE + void EnumMemoryRegions(CLRDataEnumMemoryFlags flags); +#endif +}; +typedef DPTR(FixupPrecode) PTR_FixupPrecode; + + +// Precode to shuffle this and retbuf for closed delegates over static methods with return buffer +struct ThisPtrRetBufPrecode { + + static const int Type = 0x84; + + // mov r12, r0 + // mov r0, r1 + // mov r1, r12 + // ldr pc, [pc, #0] ; =m_pTarget + // dcd pTarget + // dcd pMethodDesc + WORD m_rgCode[6]; + TADDR m_pTarget; + TADDR m_pMethodDesc; + + void Init(MethodDesc* pMD, LoaderAllocator *pLoaderAllocator); + + TADDR GetMethodDesc() + { + LIMITED_METHOD_DAC_CONTRACT; + + return m_pMethodDesc; + } + + PCODE GetTarget() + { + LIMITED_METHOD_DAC_CONTRACT; + return m_pTarget; + } + + BOOL SetTargetInterlocked(TADDR target, TADDR expected) + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + } + CONTRACTL_END; + + EnsureWritableExecutablePages(&m_pTarget); + return FastInterlockCompareExchange((LONG*)&m_pTarget, (LONG)target, (LONG)expected) == (LONG)expected; + } +}; +typedef DPTR(ThisPtrRetBufPrecode) PTR_ThisPtrRetBufPrecode; + + +#ifdef HAS_REMOTING_PRECODE + +// Precode with embedded remoting interceptor +struct RemotingPrecode { + + static const int Type = 0x02; + + // push {r1,lr} + // ldr r1, [pc, #16] ; =m_pPrecodeRemotingThunk + // blx r1 + // pop {r1,lr} + // ldr pc, [pc, #12] ; =m_pLocalTarget + // nop ; padding for alignment + // dcd m_pMethodDesc + // dcd m_pPrecodeRemotingThunk + // dcd m_pLocalTarget + WORD m_rgCode[8]; + TADDR m_pMethodDesc; + TADDR m_pPrecodeRemotingThunk; + TADDR m_pLocalTarget; + + void Init(MethodDesc* pMD, LoaderAllocator *pLoaderAllocator = NULL); + + TADDR GetMethodDesc() + { + LIMITED_METHOD_DAC_CONTRACT; + return m_pMethodDesc; + } + + PCODE GetTarget() + { + LIMITED_METHOD_DAC_CONTRACT; + return m_pLocalTarget; + } + + BOOL SetTargetInterlocked(TADDR target, TADDR expected) + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + } + CONTRACTL_END; + + EnsureWritableExecutablePages(&m_pLocalTarget); + return FastInterlockCompareExchange((LONG*)&m_pLocalTarget, (LONG)target, (LONG)expected) == (LONG)expected; + } + +#ifdef FEATURE_PREJIT + void Fixup(DataImage *image, ZapNode *pCodeNode); +#endif +}; +typedef DPTR(RemotingPrecode) PTR_RemotingPrecode; + +EXTERN_C void PrecodeRemotingThunk(); + +#endif // HAS_REMOTING_PRECODE + +//********************************************************************** +// Miscellaneous +//********************************************************************** + +// Given the first halfword value of an ARM (Thumb) instruction (which is either an entire +// 16-bit instruction, or the high-order halfword of a 32-bit instruction), determine how many bytes +// the instruction is (2 or 4) and return that. +inline size_t GetARMInstructionLength(WORD instr) +{ + // From the ARM Architecture Reference Manual, A6.1 "Thumb instruction set encoding": + // If bits [15:11] of the halfword being decoded take any of the following values, the halfword is the first + // halfword of a 32-bit instruction: + // 0b11101 + // 0b11110 + // 0b11111 + // Otherwise, the halfword is a 16-bit instruction. + if ((instr & 0xf800) > 0xe000) + { + return 4; + } + else + { + return 2; + } +} + +// Given a pointer to an ARM (Thumb) instruction address, determine how many bytes +// the instruction is (2 or 4) and return that. +inline size_t GetARMInstructionLength(PBYTE pInstr) +{ + return GetARMInstructionLength(*(WORD*)pInstr); +} + +EXTERN_C void FCallMemcpy(BYTE* dest, BYTE* src, int len); + +#endif // __cgencpu_h__ diff --git a/src/vm/arm/crthelpers.S b/src/vm/arm/crthelpers.S new file mode 100644 index 0000000000..b561f2790c --- /dev/null +++ b/src/vm/arm/crthelpers.S @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== +// *********************************************************************** +// File: crthelpers.S +// +// *********************************************************************** + +#include "unixasmmacros.inc" +#include "asmconstants.h" + +.syntax unified +.thumb + +// JIT_MemSet/JIT_MemCpy +// +// It is IMPORANT that the exception handling code is able to find these guys +// on the stack, but to keep them from being tailcalled by VC++ we need to turn +// off optimization and it ends up being a wasteful implementation. +// +// Hence these assembly helpers. +// +//EXTERN_C void __stdcall JIT_MemSet(void* _dest, int c, size_t count) +LEAF_ENTRY JIT_MemSet, _TEXT + + cmp r2, #0 + it eq + bxeq lr + + ldr r3, [r0] + + b C_PLTFUNC(memset) + +LEAF_END_MARKED JIT_MemSet, _TEXT + + +//EXTERN_C void __stdcall JIT_MemCpy(void* _dest, const void *_src, size_t count) +LEAF_ENTRY JIT_MemCpy, _TEXT +// +// It only requires 4 byte alignment +// and doesn't return a value + + cmp r2, #0 + it eq + bxeq lr + + ldr r3, [r0] + ldr r3, [r1] + + b C_PLTFUNC(memcpy) + +LEAF_END_MARKED JIT_MemCpy, _TEXT + diff --git a/src/vm/arm/ehhelpers.S b/src/vm/arm/ehhelpers.S new file mode 100644 index 0000000000..88afc43415 --- /dev/null +++ b/src/vm/arm/ehhelpers.S @@ -0,0 +1,149 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== + +#include "unixasmmacros.inc" +#include "asmconstants.h" + +.syntax unified +.thumb + +// +// WARNING!! These functions immediately ruin thread unwindability. This is +// WARNING!! OK as long as there is a mechanism for saving the thread context +// WARNING!! prior to running these functions as well as a mechanism for +// WARNING!! restoring the context prior to any stackwalk. This means that +// WARNING!! we need to ensure that no GC can occur while the stack is +// WARNING!! unwalkable. This further means that we cannot allow any exception +// WARNING!! to occur when the stack is unwalkable +// + + // GSCookie + alignment padding +OFFSET_OF_FRAME=(4 + SIZEOF__GSCookie) + + .macro GenerateRedirectedStubWithFrame STUB, TARGET + + // + // This is the primary function to which execution will be redirected to. + // + NESTED_ENTRY \STUB, _TEXT, NoHandler + + // + // IN: lr: original IP before redirect + // + + PROLOG_PUSH "{r4,r7,lr}" + alloc_stack OFFSET_OF_FRAME + SIZEOF__FaultingExceptionFrame + + // At this point, the stack maybe misaligned if the thread abort was asynchronously + // triggered in the prolog or epilog of the managed method. For such a case, we must + // align the stack before calling into the VM. + // + // Runtime check for 8-byte alignment. + PROLOG_STACK_SAVE r7 + // We lose stack unwindability here by configuring fp(r7) incorrectely + // here. + and r0, r7, #4 + sub sp, sp, r0 + + // Save pointer to FEF for GetFrameFromRedirectedStubStackFrame + add r4, sp, #OFFSET_OF_FRAME + + // Prepare to initialize to NULL + mov r1,#0 + str r1, [r4] // Initialize vtbl (it is not strictly necessary) + str r1, [r4, #FaultingExceptionFrame__m_fFilterExecuted] // Initialize BOOL for personality routine + + mov r0, r4 // move the ptr to FEF in R0 + + // stack must be 8 byte aligned + CHECK_STACK_ALIGNMENT + + bl C_FUNC(\TARGET) + + // Target should not return. + EMIT_BREAKPOINT + + NESTED_END \STUB, _TEXT + + .endm + +// ------------------------------------------------------------------ +// +// Helpers for async (NullRef, AccessViolation) exceptions +// + + NESTED_ENTRY NakedThrowHelper2,_TEXT,FixContextHandler + push {r0, lr} + + // On entry: + // + // R0 = Address of FaultingExceptionFrame + bl C_FUNC(LinkFrameAndThrow) + + // Target should not return. + EMIT_BREAKPOINT + + NESTED_END NakedThrowHelper2, _TEXT + + + GenerateRedirectedStubWithFrame NakedThrowHelper, NakedThrowHelper2 + +// ------------------------------------------------------------------ + + // This helper enables us to call into a funclet after applying the non-volatiles + NESTED_ENTRY CallEHFunclet, _TEXT, NoHandler + + PROLOG_PUSH "{r4-r11, lr}" + PROLOG_STACK_SAVE_OFFSET r7, #12 + alloc_stack 4 + + // On entry: + // + // R0 = throwable + // R1 = PC to invoke + // R2 = address of R4 register in CONTEXT record// used to restore the non-volatile registers of CrawlFrame + // R3 = address of the location where the SP of funclet's caller (i.e. this helper) should be saved. + // + // Save the SP of this function + str sp, [r3] + // apply the non-volatiles corresponding to the CrawlFrame + ldm r2!, {r4-r6} + add r2, r2, #4 + ldm r2!, {r8-r11} + // Invoke the funclet + blx r1 + + free_stack 4 + EPILOG_POP "{r4-r11, pc}" + + NESTED_END CallEHFunclet, _TEXT + + // This helper enables us to call into a filter funclet by passing it the CallerSP to lookup the + // frame pointer for accessing the locals in the parent method. + NESTED_ENTRY CallEHFilterFunclet, _TEXT, NoHandler + + PROLOG_PUSH "{r7, lr}" + PROLOG_STACK_SAVE r7 + + // On entry: + // + // R0 = throwable + // R1 = SP of the caller of the method/funclet containing the filter + // R2 = PC to invoke + // R3 = address of the location where the SP of funclet's caller (i.e. this helper) should be saved. + // + // Save the SP of this function + str sp, [r3] + // Invoke the filter funclet + blx r2 + + EPILOG_POP "{r7, pc}" + + NESTED_END CallEHFilterFunclet, _TEXT diff --git a/src/vm/arm/ehhelpers.asm b/src/vm/arm/ehhelpers.asm new file mode 100644 index 0000000000..ac26c7eaf4 --- /dev/null +++ b/src/vm/arm/ehhelpers.asm @@ -0,0 +1,182 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; ==++== +; + +; +; ==--== +#include "ksarm.h" + +#include "asmconstants.h" + +#include "asmmacros.h" + + IMPORT FixContextHandler + IMPORT LinkFrameAndThrow + IMPORT HijackHandler + IMPORT ThrowControlForThread + +; +; WARNING!! These functions immediately ruin thread unwindability. This is +; WARNING!! OK as long as there is a mechanism for saving the thread context +; WARNING!! prior to running these functions as well as a mechanism for +; WARNING!! restoring the context prior to any stackwalk. This means that +; WARNING!! we need to ensure that no GC can occur while the stack is +; WARNING!! unwalkable. This further means that we cannot allow any exception +; WARNING!! to occur when the stack is unwalkable +; + + TEXTAREA + + ; GSCookie, scratch area + GBLA OFFSET_OF_FRAME + + ; GSCookie + alignment padding +OFFSET_OF_FRAME SETA 4 + SIZEOF__GSCookie + + MACRO + GenerateRedirectedStubWithFrame $STUB, $TARGET + + ; + ; This is the primary function to which execution will be redirected to. + ; + NESTED_ENTRY $STUB + + ; + ; IN: lr: original IP before redirect + ; + + PROLOG_PUSH {r4,r7,lr} + PROLOG_STACK_ALLOC OFFSET_OF_FRAME + SIZEOF__FaultingExceptionFrame + + ; At this point, the stack maybe misaligned if the thread abort was asynchronously + ; triggered in the prolog or epilog of the managed method. For such a case, we must + ; align the stack before calling into the VM. + ; + ; Runtime check for 8-byte alignment. + PROLOG_STACK_SAVE r7 + and r0, r7, #4 + sub sp, sp, r0 + + ; Save pointer to FEF for GetFrameFromRedirectedStubStackFrame + add r4, sp, #OFFSET_OF_FRAME + + ; Prepare to initialize to NULL + mov r1,#0 + str r1, [r4] ; Initialize vtbl (it is not strictly necessary) + str r1, [r4, #FaultingExceptionFrame__m_fFilterExecuted] ; Initialize BOOL for personality routine + + mov r0, r4 ; move the ptr to FEF in R0 + + ; stack must be 8 byte aligned + CHECK_STACK_ALIGNMENT + + bl $TARGET + + ; Target should not return. + EMIT_BREAKPOINT + + NESTED_END $STUB + + MEND + +; ------------------------------------------------------------------ +; +; Helpers for async (NullRef, AccessViolation) exceptions +; + + NESTED_ENTRY NakedThrowHelper2,,FixContextHandler + PROLOG_PUSH {r0, lr} + + ; On entry: + ; + ; R0 = Address of FaultingExceptionFrame + bl LinkFrameAndThrow + + ; Target should not return. + EMIT_BREAKPOINT + + NESTED_END NakedThrowHelper2 + + + GenerateRedirectedStubWithFrame NakedThrowHelper, NakedThrowHelper2 + +; ------------------------------------------------------------------ +; +; Helpers for ThreadAbort exceptions +; + + NESTED_ENTRY RedirectForThreadAbort2,,HijackHandler + PROLOG_PUSH {r0, lr} + + ; stack must be 8 byte aligned + CHECK_STACK_ALIGNMENT + + ; On entry: + ; + ; R0 = Address of FaultingExceptionFrame. + ; + ; Invoke the helper to setup the FaultingExceptionFrame and raise the exception + bl ThrowControlForThread + + ; ThrowControlForThread doesn't return. + EMIT_BREAKPOINT + + NESTED_END RedirectForThreadAbort2 + + GenerateRedirectedStubWithFrame RedirectForThreadAbort, RedirectForThreadAbort2 + +; ------------------------------------------------------------------ + + ; This helper enables us to call into a funclet after applying the non-volatiles + NESTED_ENTRY CallEHFunclet + + PROLOG_PUSH {r4-r11, lr} + PROLOG_STACK_ALLOC 4 + + ; On entry: + ; + ; R0 = throwable + ; R1 = PC to invoke + ; R2 = address of R4 register in CONTEXT record; used to restore the non-volatile registers of CrawlFrame + ; R3 = address of the location where the SP of funclet's caller (i.e. this helper) should be saved. + ; + ; Save the SP of this function + str sp, [r3] + ; apply the non-volatiles corresponding to the CrawlFrame + ldm r2, {r4-r11} + ; Invoke the funclet + blx r1 + + EPILOG_STACK_FREE 4 + EPILOG_POP {r4-r11, pc} + + NESTED_END CallEHFunclet + + ; This helper enables us to call into a filter funclet by passing it the CallerSP to lookup the + ; frame pointer for accessing the locals in the parent method. + NESTED_ENTRY CallEHFilterFunclet + + PROLOG_PUSH {lr} + PROLOG_STACK_ALLOC 4 + + ; On entry: + ; + ; R0 = throwable + ; R1 = SP of the caller of the method/funclet containing the filter + ; R2 = PC to invoke + ; R3 = address of the location where the SP of funclet's caller (i.e. this helper) should be saved. + ; + ; Save the SP of this function + str sp, [r3] + ; Invoke the filter funclet + blx r2 + + EPILOG_STACK_FREE 4 + EPILOG_POP {pc} + + NESTED_END CallEHFilterFunclet + END + diff --git a/src/vm/arm/exceparm.cpp b/src/vm/arm/exceparm.cpp new file mode 100644 index 0000000000..6852adcc33 --- /dev/null +++ b/src/vm/arm/exceparm.cpp @@ -0,0 +1,144 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +// +// File: ExcepArm.cpp + +#include "common.h" +#include "asmconstants.h" +#include "virtualcallstub.h" + +PTR_CONTEXT GetCONTEXTFromRedirectedStubStackFrame(T_DISPATCHER_CONTEXT * pDispatcherContext) +{ + LIMITED_METHOD_DAC_CONTRACT; + + UINT_PTR stackSlot = pDispatcherContext->EstablisherFrame + REDIRECTSTUB_SP_OFFSET_CONTEXT; + PTR_PTR_CONTEXT ppContext = dac_cast((TADDR)stackSlot); + return *ppContext; +} + +PTR_CONTEXT GetCONTEXTFromRedirectedStubStackFrame(T_CONTEXT * pContext) +{ + LIMITED_METHOD_DAC_CONTRACT; + + UINT_PTR stackSlot = pContext->Sp + REDIRECTSTUB_SP_OFFSET_CONTEXT; + PTR_PTR_CONTEXT ppContext = dac_cast((TADDR)stackSlot); + return *ppContext; +} + +#if !defined(DACCESS_COMPILE) + +// The next two functions help retrieve data kept relative to FaultingExceptionFrame that is setup +// for handling async exceptions (e.g. AV, NullRef, ThreadAbort, etc). +// +// FEF (and related data) is available relative to R4 - the thing to be kept in mind is that the +// DispatcherContext->ContextRecord: +// +// 1) represents the caller context in the first pass. +// 2) represents the current context in the second pass. +// +// Since R4 is a non-volatile register, this works for us since we setup the value of R4 +// in the redirection helpers (e.g. NakedThrowHelper or RedirectForThreadAbort) but do not +// change it in their respective callee functions (e.g. NakedThrowHelper2 or RedirectForThreadAbort2) +// that have the personality routines associated with them (which perform the collided unwind and also +// invoke the two functions below). +// +// Thus, when our personality routine gets called in either passes, DC->ContextRecord->R4 will +// have the same value. + +// Returns the pointer to the FEF +FaultingExceptionFrame *GetFrameFromRedirectedStubStackFrame (T_DISPATCHER_CONTEXT *pDispatcherContext) +{ + LIMITED_METHOD_CONTRACT; + + return (FaultingExceptionFrame*)((TADDR)pDispatcherContext->ContextRecord->R4); +} + +//Return TRUE if pContext->Pc is in VirtualStub +BOOL IsIPinVirtualStub(PCODE f_IP) +{ + LIMITED_METHOD_CONTRACT; + + Thread * pThread = GetThread(); + + // We may not have a managed thread object. Example is an AV on the helper thread. + // (perhaps during StubManager::IsStub) + if (pThread == NULL) + { + return FALSE; + } + + VirtualCallStubManager::StubKind sk; + VirtualCallStubManager::FindStubManager(f_IP, &sk); + + if (sk == VirtualCallStubManager::SK_DISPATCH) + { + return TRUE; + } + else if (sk == VirtualCallStubManager::SK_RESOLVE) + { + return TRUE; + } + + else { + return FALSE; + } +} + + +// Returns TRUE if caller should resume execution. +BOOL +AdjustContextForVirtualStub( + EXCEPTION_RECORD *pExceptionRecord, + CONTEXT *pContext) +{ + LIMITED_METHOD_CONTRACT; + + Thread * pThread = GetThread(); + + // We may not have a managed thread object. Example is an AV on the helper thread. + // (perhaps during StubManager::IsStub) + if (pThread == NULL) + { + return FALSE; + } + + PCODE f_IP = GetIP(pContext); + TADDR pInstr = PCODEToPINSTR(f_IP); + + VirtualCallStubManager::StubKind sk; + VirtualCallStubManager::FindStubManager(f_IP, &sk); + + if (sk == VirtualCallStubManager::SK_DISPATCH) + { + if (*PTR_WORD(pInstr) != DISPATCH_STUB_FIRST_WORD) + { + _ASSERTE(!"AV in DispatchStub at unknown instruction"); + return FALSE; + } + } + else + if (sk == VirtualCallStubManager::SK_RESOLVE) + { + if (*PTR_WORD(pInstr) != RESOLVE_STUB_FIRST_WORD) + { + _ASSERTE(!"AV in ResolveStub at unknown instruction"); + return FALSE; + } + } + else + { + return FALSE; + } + + PCODE callsite = GetAdjustedCallAddress(GetLR(pContext)); + + // Lr must already have been saved before calling so it should not be necessary to restore Lr + + pExceptionRecord->ExceptionAddress = (PVOID)callsite; + SetIP(pContext, callsite); + + return TRUE; +} +#endif // !DACCESS_COMPILE + diff --git a/src/vm/arm/excepcpu.h b/src/vm/arm/excepcpu.h new file mode 100644 index 0000000000..f13d81fdcb --- /dev/null +++ b/src/vm/arm/excepcpu.h @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +// +// + + +#ifndef __excepcpu_h__ +#define __excepcpu_h__ + +#define THROW_CONTROL_FOR_THREAD_FUNCTION RedirectForThreadAbort +EXTERN_C void RedirectForThreadAbort(); + +#define STATUS_CLR_GCCOVER_CODE STATUS_ILLEGAL_INSTRUCTION + +class Thread; +class FaultingExceptionFrame; + +#define INSTALL_EXCEPTION_HANDLING_RECORD(record) +#define UNINSTALL_EXCEPTION_HANDLING_RECORD(record) +// +// On ARM, the COMPlusFrameHandler's work is done by our personality routine. +// +#define DECLARE_CPFH_EH_RECORD(pCurThread) + +// +// Retrieves the redirected CONTEXT* from the stack frame of one of the +// RedirectedHandledJITCaseForXXX_Stub's. +// +PTR_CONTEXT GetCONTEXTFromRedirectedStubStackFrame(T_DISPATCHER_CONTEXT * pDispatcherContext); +PTR_CONTEXT GetCONTEXTFromRedirectedStubStackFrame(T_CONTEXT * pContext); + +// +// Retrieves the FaultingExceptionFrame* from the stack frame of +// RedirectForThrowControl or NakedThrowHelper. +// +FaultingExceptionFrame *GetFrameFromRedirectedStubStackFrame (T_DISPATCHER_CONTEXT *pDispatcherContext); + +inline +PCODE GetAdjustedCallAddress(PCODE returnAddress) +{ + LIMITED_METHOD_CONTRACT; + + // blx instruction size is 2 bytes + return returnAddress - 2; +} + +BOOL AdjustContextForVirtualStub(EXCEPTION_RECORD *pExceptionRecord, T_CONTEXT *pContext); +BOOL IsIPinVirtualStub(PCODE f_IP); + +#endif // __excepcpu_h__ diff --git a/src/vm/arm/gmscpu.h b/src/vm/arm/gmscpu.h new file mode 100644 index 0000000000..dee60633ad --- /dev/null +++ b/src/vm/arm/gmscpu.h @@ -0,0 +1,175 @@ +// 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. + +/**************************************************************/ +/* gmscpu.h */ +/**************************************************************/ +/* HelperFrame is defines 'GET_STATE(machState)' macro, which + figures out what the state of the machine will be when the + current method returns. It then stores the state in the + JIT_machState structure. */ + +/**************************************************************/ + +#ifndef __gmscpu_h__ +#define __gmscpu_h__ + +#define __gmscpu_h__ + +#ifdef _DEBUG +class HelperMethodFrame; +struct MachState; +EXTERN_C MachState* __stdcall HelperMethodFrameConfirmState(HelperMethodFrame* frame, void* esiVal, void* ediVal, void* ebxVal, void* ebpVal); +#endif + + // A MachState indicates the register state of the processor at some point in time (usually + // just before or after a call is made). It can be made one of two ways. Either explicitly + // (when you for some reason know the values of all the registers), or implicitly using the + // GET_STATE macros. + +typedef DPTR(struct MachState) PTR_MachState; +struct MachState { + + BOOL isValid() { LIMITED_METHOD_DAC_CONTRACT; return _isValid; } + TADDR GetRetAddr() { LIMITED_METHOD_DAC_CONTRACT; return _pc; } + + friend class HelperMethodFrame; + friend class CheckAsmOffsets; + friend struct LazyMachState; + + +protected: + // The simplest way to understand the relationship between capturedR4_R11 (registers + // representing the captured state) and _R4_R11 (pointers to registers representing + // preserved state) is as follows: + // + // 1) LazyMachState::unwindLazyState is invoked by HelperMethodFrame to initialize the captured + // state. It then performs an unwind and copies the register pointers to _R4_R11. + // + // 2) HelperMethodFrame::UpdateRegdisplay is invoked by our StackWalker that initializes + // the regdisplay with the updated register state. + // + // 3) HelperMethodFrameRestoreState is invoked when the HMF state machine exits and it + // restores the values of unmodified registers. + + TADDR captureR4_R11[8]; // Registers R4..R11 at the time of capture + + PTR_DWORD _R4_R11[8]; // Preserved registers + + TADDR _pc; // program counter after the function returns + TADDR _sp; // stack pointer after the function returns + + BOOL _isValid; +}; + +/********************************************************************/ +/* This allows you to defer the computation of the Machine state + until later. Note that we don't reuse slots, because we want + this to be threadsafe without locks */ + +struct LazyMachState : public MachState { + // compute the machine state of the processor as it will exist just + // after the return after at most'funCallDepth' number of functions. + // if 'testFtn' is non-NULL, the return address is tested at each + // return instruction encountered. If this test returns non-NULL, + // then stack walking stops (thus you can walk up to the point that the + // return address matches some criteria + + // Normally this is called with funCallDepth=1 and testFtn = 0 so that + // it returns the state of the processor after the function that called 'captureState()' + void setLazyStateFromUnwind(MachState* copy); + static void unwindLazyState(LazyMachState* baseState, + MachState* lazyState, + DWORD threadId, + int funCallDepth = 1, + HostCallPreference hostCallPreference = AllowHostCalls); + + friend class HelperMethodFrame; + friend class CheckAsmOffsets; +private: + TADDR captureSp; // Stack pointer at the time of capture + TADDR captureIp; // Instruction pointer at the time of capture +}; + +// R4 - R11 +#define NUM_NONVOLATILE_CONTEXT_POINTERS 8 + +inline void LazyMachState::setLazyStateFromUnwind(MachState* copy) +{ + LIMITED_METHOD_CONTRACT; + +#if defined(DACCESS_COMPILE) + // This function cannot be called in DAC because DAC cannot update target memory. + DacError(E_FAIL); + return; + +#else // !DACCESS_COMPILE + this->_pc = copy->_pc; + this->_sp = copy->_sp; + + // Capture* has already been set, so there is no need to touch it. + // This was setup in LazyMachState::unwindLazyState just before we + // called into the OS for unwind. + + // Prepare to loop over the nonvolatile context pointers for and + // make sure to properly copy interior pointers into the new struct. + + PDWORD* pSrc = ©->_R4_R11[0]; + PDWORD* pDst = &this->_R4_R11[0]; + + const PDWORD LowerBoundDst = (PDWORD) this; + const PDWORD LowerBoundSrc = (PDWORD) copy; + + // Calculate the upperbound till which we need to loop (i.e. the highest address till + // which we have saved non-volatile pointers). + const PDWORD UpperBoundSrc = (PDWORD) (((BYTE*)LowerBoundSrc) + offsetof(LazyMachState, _pc)); + +#ifdef _DEBUG + int count = 0; +#endif // _DEBUG + + while (((PDWORD)pSrc) < UpperBoundSrc) + { +#ifdef _DEBUG + count++; +#endif // _DEBUG + + PDWORD valueSrc = *pSrc++; + + // If any non-volatile register pointer is pointing to the corresponding register field + // in the MachState, then make the corresponding pointer in "this" MachState point + // to the corresponding field. + if ((LowerBoundSrc <= valueSrc) && (valueSrc < UpperBoundSrc)) + { + valueSrc = (PDWORD)((BYTE*)valueSrc - (BYTE*)LowerBoundSrc + (BYTE*)LowerBoundDst); + } + + *pDst++ = valueSrc; + } + + CONSISTENCY_CHECK_MSGF(count == NUM_NONVOLATILE_CONTEXT_POINTERS, ("count != NUM_NONVOLATILE_CONTEXT_POINTERS, actually = %d", count)); + + // this has to be last because we depend on write ordering to + // synchronize the race implicit in updating this struct + VolatileStore(&_isValid, TRUE); + +#endif // !DACCESS_COMPILE + +} +typedef DPTR(LazyMachState) PTR_LazyMachState; + +// Do the initial capture of the machine state. This is meant to be +// as light weight as possible, as we may never need the state that +// we capture. Thus to complete the process you need to call +// 'getMachState()', which finishes the process +EXTERN_C void LazyMachStateCaptureState(struct LazyMachState *pState); + +// CAPTURE_STATE captures just enough register state so that the state of the +// processor can be deterined just after the the routine that has CAPTURE_STATE in +// it returns. + +#define CAPTURE_STATE(machState, ret) \ + LazyMachStateCaptureState(machState) + +#endif diff --git a/src/vm/arm/jithelpersarm.cpp b/src/vm/arm/jithelpersarm.cpp new file mode 100644 index 0000000000..40827dd712 --- /dev/null +++ b/src/vm/arm/jithelpersarm.cpp @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =========================================================================== +// File: JITHelpersARM.CPP +// =========================================================================== + +// This contains JITinterface routines that are specific to the +// ARM platform. They are modeled after the AMD64 specific routines +// found in JIThelpersAMD64.cpp + + +#include "common.h" +#include "jitinterface.h" +#include "eeconfig.h" +#include "excep.h" +#include "ecall.h" +#include "asmconstants.h" + +EXTERN_C void JIT_TailCallHelperStub_ReturnAddress(); + +TailCallFrame * TailCallFrame::GetFrameFromContext(CONTEXT * pContext) +{ + _ASSERTE((void*)::GetIP(pContext) == JIT_TailCallHelperStub_ReturnAddress); + return (TailCallFrame*)(pContext->R7 - offsetof(TailCallFrame, m_calleeSavedRegisters)); +} + +// Assuming pContext is a plain generic call-site, adjust it to look like +// it called into TailCallHelperStub, and is at the point of the call. +TailCallFrame * TailCallFrame::AdjustContextForTailCallHelperStub(CONTEXT * pContext, size_t cbNewArgArea, Thread * pThread) +{ + TailCallFrame * pNewFrame = (TailCallFrame *) (GetSP(pContext) - sizeof(TailCallFrame)); + + // The return addres for the pseudo-call + pContext->Lr = (DWORD_PTR)JIT_TailCallHelperStub_ReturnAddress; + // The R11/ETW chain 'frame' pointer + pContext->R11 = GetSP(pContext) - (2 * sizeof(DWORD)); // LR & R11 + // The unwind data frame pointer + pContext->R7 = pContext->R11 - (7 * sizeof(DWORD)); // r4-R10 non-volatile registers + // for the args and the remainder of the FrameWithCookie + SetSP(pContext, (size_t) pNewFrame - (cbNewArgArea + sizeof(GSCookie))); + + // For popping the Frame, store the Thread + pContext->R6 = (DWORD_PTR)pThread; + // And the current head/top + pContext->R5 = (DWORD_PTR)pThread->GetFrame(); + + return pNewFrame; +} + diff --git a/src/vm/arm/memcpy.S b/src/vm/arm/memcpy.S new file mode 100644 index 0000000000..b9788605f7 --- /dev/null +++ b/src/vm/arm/memcpy.S @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "unixasmmacros.inc" +#include "asmconstants.h" + +.syntax unified +.thumb + +// +// void *memcpy(void *dst, const void *src, size_t length) +// +// Copy a block of memory in a forward direction. +// + + NESTED_ENTRY FCallMemcpy, _TEXT, NoHandler + cmp r2, #0 + + beq LOCAL_LABEL(GC_POLL) + + PROLOG_PUSH "{r7, lr}" + PROLOG_STACK_SAVE r7 + + ldr r3, [r0] + ldr r3, [r1] + + blx C_FUNC(memcpy) + + EPILOG_POP "{r7, pc}" + +LOCAL_LABEL(GC_POLL): + ldr r0, =g_TrapReturningThreads + ldr r0, [r0] + cmp r0, #0 + it ne + bne C_FUNC(FCallMemCpy_GCPoll) + bx lr + NESTED_END_MARKED FCallMemcpy, _TEXT diff --git a/src/vm/arm/memcpy.asm b/src/vm/arm/memcpy.asm new file mode 100644 index 0000000000..9a0e7d373f --- /dev/null +++ b/src/vm/arm/memcpy.asm @@ -0,0 +1,284 @@ +; 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. + +; + +; + +; This is the fast memcpy implementation for ARM stolen from the CRT (original location +; vctools\crt\crtw32\string\arm\memcpy.asm) and modified to be compatible with CLR. +; +; For reference, the unmodified crt version of memcpy is preserved as memcpy_crt.asm + +#include "ksarm.h" +#include "asmmacros.h" + + IMPORT FCallMemCpy_GCPoll + IMPORT g_TrapReturningThreads + + AREA |.text|,ALIGN=5,CODE,READONLY + +; +; void *memcpy(void *dst, const void *src, size_t length) +; +; Copy a block of memory in a forward direction. +; + + ALIGN 32 + LEAF_ENTRY FCallMemcpy + + pld [r1] ; preload the first cache line + cmp r2, #16 ; less than 16 bytes? + mov r3, r0 ; use r3 as our destination + bhs.W __FCallMemcpy_large ; go to the large copy case directly. ".W" indicates encoding using 32bits + +CpySmal tbb [pc, r2] ; branch to specialized bits for small copies +__SwitchTable1_Copy +CTable dcb (Copy0 - CTable) / 2 ; 0B + dcb (Copy1 - CTable) / 2 ; 1B + dcb (Copy2 - CTable) / 2 ; 2B + dcb (Copy3 - CTable) / 2 ; 3B + dcb (Copy4 - CTable) / 2 ; 4B + dcb (Copy5 - CTable) / 2 ; 5B + dcb (Copy6 - CTable) / 2 ; 6B + dcb (Copy7 - CTable) / 2 ; 7B + dcb (Copy8 - CTable) / 2 ; 8B + dcb (Copy9 - CTable) / 2 ; 9B + dcb (Copy10 - CTable) / 2 ; 10B + dcb (Copy11 - CTable) / 2 ; 11B + dcb (Copy12 - CTable) / 2 ; 12B + dcb (Copy13 - CTable) / 2 ; 13B + dcb (Copy14 - CTable) / 2 ; 14B + dcb (Copy15 - CTable) / 2 ; 15B +__SwitchTableEnd_Copy + +Copy1 ldrb r2, [r1] + strb r2, [r3] +Copy0 b GC_POLL + +Copy2 ldrh r2, [r1] + strh r2, [r3] + b GC_POLL + +Copy3 ldrh r2, [r1] + ldrb r1, [r1, #2] + strh r2, [r3] + strb r1, [r3, #2] + b GC_POLL + +Copy4 ldr r2, [r1] + str r2, [r3] + b GC_POLL + +Copy5 ldr r2, [r1] + ldrb r1, [r1, #4] + str r2, [r3] + strb r1, [r3, #4] + b GC_POLL + +Copy6 ldr r2, [r1] + ldrh r1, [r1, #4] + str r2, [r3] + strh r1, [r3, #4] + b GC_POLL + +Copy7 ldr r12, [r1] + ldrh r2, [r1, #4] + ldrb r1, [r1, #6] + str r12, [r3] + strh r2, [r3, #4] + strb r1, [r3, #6] + b GC_POLL + +Copy8 ldr r2, [r1] + ldr r1, [r1, #4] + str r2, [r3] + str r1, [r3, #4] + b GC_POLL + +Copy9 ldr r12, [r1] + ldr r2, [r1, #4] + ldrb r1, [r1, #8] + str r12, [r3] + str r2, [r3, #4] + strb r1, [r3, #8] + b GC_POLL + +Copy10 ldr r12, [r1] + ldr r2, [r1, #4] + ldrh r1, [r1, #8] + str r12, [r3] + str r2, [r3, #4] + strh r1, [r3, #8] + b GC_POLL + +Copy11 ldr r12, [r1] + ldr r2, [r1, #4] + str r12, [r3] + str r2, [r3, #4] + ldrh r2, [r1, #8] + ldrb r1, [r1, #10] + strh r2, [r3, #8] + strb r1, [r3, #10] + b GC_POLL + +Copy12 ldr r12, [r1] + ldr r2, [r1, #4] + ldr r1, [r1, #8] + str r12, [r3] + str r2, [r3, #4] + str r1, [r3, #8] + b GC_POLL + +Copy13 ldr r12, [r1] + ldr r2, [r1, #4] + str r12, [r3] + str r2, [r3, #4] + ldr r2, [r1, #8] + ldrb r1, [r1, #12] + str r2, [r3, #8] + strb r1, [r3, #12] + b GC_POLL + +Copy14 ldr r12, [r1] + ldr r2, [r1, #4] + str r12, [r3] + str r2, [r3, #4] + ldr r2, [r1, #8] + ldrh r1, [r1, #12] + str r2, [r3, #8] + strh r1, [r3, #12] + b GC_POLL + +Copy15 ldr r12, [r1] + ldr r2, [r1, #4] + str r12, [r3] + str r2, [r3, #4] + ldr r12, [r1, #8] + ldrh r2, [r1, #12] + ldrb r1, [r1, #14] + str r12, [r3, #8] + strh r2, [r3, #12] + strb r1, [r3, #14] +GC_POLL + ldr r0, =g_TrapReturningThreads + ldr r0, [r0] + cmp r0, #0 + bne FCallMemCpy_GCPoll + + bx lr + + LEAF_END FCallMemcpy + + +; +; __memcpy_forward_large_integer (internal calling convention) +; +; Copy large (>= 16 bytes) blocks of memory in a forward direction, +; using integer registers only. +; + + ALIGN 32 + NESTED_ENTRY __FCallMemcpy_large + + PROLOG_NOP lsls r12, r3, #31 ; C = bit 1, N = bit 0 + PROLOG_PUSH {r4-r9, r11, lr} + +; +; Align destination to a word boundary +; + + bpl %F1 + ldrb r4, [r1], #1 ; fetch byte + subs r2, r2, #1 ; decrement count + strb r4, [r3], #1 ; store byte + lsls r12, r3, #31 ; compute updated status +1 + bcc %F2 ; if already aligned, just skip ahead + ldrh r4, [r1], #2 ; fetch halfword + subs r2, r2, #2 ; decrement count + strh r4, [r3], #2 ; store halfword +2 + tst r1, #3 ; is the source now word-aligned? + bne %F20 ; if not, we have to use the slow path + +; +; Source is word-aligned; fast case +; + +10 + subs r2, r2, #32 ; take 32 off the top + blo %F13 ; if not enough, recover and do small copies + subs r2, r2, #32 ; take off another 32 + pld [r1, #32] ; pre-load one block ahead + blo %F12 ; skip the loop if that's all we have +11 + pld [r1, #64] ; prefetch ahead + subs r2, r2, #32 ; count the bytes for this block + ldm r1!, {r4-r9, r12, lr} ; load 32 bytes + stm r3!, {r4-r9, r12, lr} ; store 32 bytes + bhs %B11 ; keep going until we're done +12 + ldm r1!, {r4-r9, r12, lr} ; load 32 bytes + stm r3!, {r4-r9, r12, lr} ; store 32 bytes +13 + adds r2, r2, #(32 - 8) ; recover original count, and pre-decrement + blo %F15 ; if not enough remaining, skip this loop +14 + subs r2, r2, #8 ; decrement count + ldrd r4, r5, [r1], #8 ; fetch pair of words + strd r4, r5, [r3], #8 ; store pair of words + bhs %B14 ; loop while we still have data remaining +15 + adds r2, r2, #8 ; recover final count + + EPILOG_POP {r4-r9, r11, lr} + EPILOG_NOP bne CpySmal ; if some left, continue with small + EPILOG_BRANCH GC_POLL + +; +; Source is not word-aligned; slow case +; + +20 + subs r2, r2, #64 ; pre-decrement to simplify the loop + blo %23 ; skip over the loop if we don't have enough + pld [r1, #32] ; pre-load one block ahead +21 + pld [r1, #64] ; prefetch ahead + ldr r4, [r1, #0] ; load 32 bytes + ldr r5, [r1, #4] ; + ldr r6, [r1, #8] ; + ldr r7, [r1, #12] ; + ldr r8, [r1, #16] ; + ldr r9, [r1, #20] ; + ldr r12, [r1, #24] ; + ldr lr, [r1, #28] ; + adds r1, r1, #32 ; update pointer + subs r2, r2, #32 ; count the bytes for this block + stm r3!, {r4-r9, r12, lr} ; store 32 bytes + bhs %B21 ; keep going until we're done +23 + adds r2, r2, #(64 - 8) ; recover original count, and pre-decrement + blo %F25 ; if not enough remaining, skip this loop +24 + ldr r4, [r1] ; fetch pair of words + ldr r5, [r1, #4] ; + adds r1, r1, #8 ; update pointer + subs r2, r2, #8 ; decrement count + strd r4, r5, [r3], #8 ; store pair of words + bhs %B24 ; loop while we still have data remaining +25 + adds r2, r2, #8 ; recover final count + + EPILOG_POP {r4-r9, r11, lr} + EPILOG_NOP bne CpySmal ; if some left, continue with small + EPILOG_BRANCH GC_POLL + + EXPORT FCallMemcpy_End ; this is used to place the entire +FCallMemcpy_End ; implementation in av-exclusion list + + NESTED_END __FCallMemcpy_large + + END diff --git a/src/vm/arm/memcpy_crt.asm b/src/vm/arm/memcpy_crt.asm new file mode 100644 index 0000000000..5e3a97e3fa --- /dev/null +++ b/src/vm/arm/memcpy_crt.asm @@ -0,0 +1,1001 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; + +; + +#include "ksarm.h" + +#if !defined PF_ARM_EXTERNAL_CACHE_AVAILABLE +#define PF_ARM_EXTERNAL_CACHE_AVAILABLE 0x1a +#endif + +#if !defined(_BOOTCRT_) + + DATAAREA + +__memcpy_forward_large_func dcd __memcpy_decide + EXPORT __memcpy_forward_large_func +__memcpy_reverse_large_func dcd __memcpy_decide + EXPORT __memcpy_reverse_large_func + +#endif + + AREA |.text|,ALIGN=5,CODE,READONLY + +; +; void *memcpy(void *dst, const void *src, size_t length) +; +; Copy a block of memory in a forward direction. +; + + ALIGN 32 + LEAF_ENTRY memcpy + + ALTERNATE_ENTRY __memcpy_forward_new + + pld [r1] ; preload the first cache line + cmp r2, #16 ; less than 16 bytes? + mov r3, r0 ; use r3 as our destination + bhs CpyLrge ; go to the small copy case directly + +CpySmal tbb [pc, r2] ; branch to specialized bits for small copies +__SwitchTable1_Copy +CTable dcb (Copy0 - CTable) / 2 ; 0B + dcb (Copy1 - CTable) / 2 ; 1B + dcb (Copy2 - CTable) / 2 ; 2B + dcb (Copy3 - CTable) / 2 ; 3B + dcb (Copy4 - CTable) / 2 ; 4B + dcb (Copy5 - CTable) / 2 ; 5B + dcb (Copy6 - CTable) / 2 ; 6B + dcb (Copy7 - CTable) / 2 ; 7B + dcb (Copy8 - CTable) / 2 ; 8B + dcb (Copy9 - CTable) / 2 ; 9B + dcb (Copy10 - CTable) / 2 ; 10B + dcb (Copy11 - CTable) / 2 ; 11B + dcb (Copy12 - CTable) / 2 ; 12B + dcb (Copy13 - CTable) / 2 ; 13B + dcb (Copy14 - CTable) / 2 ; 14B + dcb (Copy15 - CTable) / 2 ; 15B +__SwitchTableEnd_Copy + +Copy1 ldrb r2, [r1] + strb r2, [r3] +Copy0 bx lr + +Copy2 ldrh r2, [r1] + strh r2, [r3] + bx lr + +Copy3 ldrh r2, [r1] + ldrb r1, [r1, #2] + strh r2, [r3] + strb r1, [r3, #2] + bx lr + +Copy4 ldr r2, [r1] + str r2, [r3] + bx lr + +Copy5 ldr r2, [r1] + ldrb r1, [r1, #4] + str r2, [r3] + strb r1, [r3, #4] + bx lr + +Copy6 ldr r2, [r1] + ldrh r1, [r1, #4] + str r2, [r3] + strh r1, [r3, #4] + bx lr + +Copy7 ldr r12, [r1] + ldrh r2, [r1, #4] + ldrb r1, [r1, #6] + str r12, [r3] + strh r2, [r3, #4] + strb r1, [r3, #6] + bx lr + +Copy8 ldr r2, [r1] + ldr r1, [r1, #4] + str r2, [r3] + str r1, [r3, #4] + bx lr + +Copy9 ldr r12, [r1] + ldr r2, [r1, #4] + ldrb r1, [r1, #8] + str r12, [r3] + str r2, [r3, #4] + strb r1, [r3, #8] + bx lr + +Copy10 ldr r12, [r1] + ldr r2, [r1, #4] + ldrh r1, [r1, #8] + str r12, [r3] + str r2, [r3, #4] + strh r1, [r3, #8] + bx lr + +Copy11 ldr r12, [r1] + ldr r2, [r1, #4] + str r12, [r3] + str r2, [r3, #4] + ldrh r2, [r1, #8] + ldrb r1, [r1, #10] + strh r2, [r3, #8] + strb r1, [r3, #10] + bx lr + +Copy12 ldr r12, [r1] + ldr r2, [r1, #4] + ldr r1, [r1, #8] + str r12, [r3] + str r2, [r3, #4] + str r1, [r3, #8] + bx lr + +Copy13 ldr r12, [r1] + ldr r2, [r1, #4] + str r12, [r3] + str r2, [r3, #4] + ldr r2, [r1, #8] + ldrb r1, [r1, #12] + str r2, [r3, #8] + strb r1, [r3, #12] + bx lr + +Copy14 ldr r12, [r1] + ldr r2, [r1, #4] + str r12, [r3] + str r2, [r3, #4] + ldr r2, [r1, #8] + ldrh r1, [r1, #12] + str r2, [r3, #8] + strh r1, [r3, #12] + bx lr + +Copy15 ldr r12, [r1] + ldr r2, [r1, #4] + str r12, [r3] + str r2, [r3, #4] + ldr r12, [r1, #8] + ldrh r2, [r1, #12] + ldrb r1, [r1, #14] + str r12, [r3, #8] + strh r2, [r3, #12] + strb r1, [r3, #14] + bx lr + +CpyLrge + +#if defined(_BOOTCRT_) + + b __memcpy_forward_large_integer ; always use integer in boot code + +#else + + eor r12, r0, r1 ; see if src/dst are equally aligned + tst r12, #3 ; at least to a 4 byte boundary + bne __memcpy_forward_large_neon ; if not, always use NEON + mov32 r12, __memcpy_forward_large_func ; otherwise, load the large function pointer + ldr pc, [r12] ; and call it + +#endif + + LEAF_END memcpy + + +; +; __memcpy_forward_large_integer (internal calling convention) +; +; Copy large (>= 16 bytes) blocks of memory in a forward direction, +; using integer registers only. +; + + ALIGN 32 + NESTED_ENTRY __memcpy_forward_large_integer_wrapper + +__memcpy_forward_large_integer + + PROLOG_NOP lsls r12, r3, #31 ; C = bit 1, N = bit 0 + PROLOG_PUSH {r4-r9, r11, lr} + +; +; Align destination to a word boundary +; + + bpl %F1 + ldrb r4, [r1], #1 ; fetch byte + subs r2, r2, #1 ; decrement count + strb r4, [r3], #1 ; store byte + lsls r12, r3, #31 ; compute updated status +1 + bcc %F2 ; if already aligned, just skip ahead + ldrh r4, [r1], #2 ; fetch halfword + subs r2, r2, #2 ; decrement count + strh r4, [r3], #2 ; store halfword +2 + tst r1, #3 ; is the source now word-aligned? + bne %F20 ; if not, we have to use the slow path + +; +; Source is word-aligned; fast case +; + +10 + subs r2, r2, #32 ; take 32 off the top + blo %F13 ; if not enough, recover and do small copies + subs r2, r2, #32 ; take off another 32 + pld [r1, #32] ; pre-load one block ahead + blo %F12 ; skip the loop if that's all we have +11 + pld [r1, #64] ; prefetch ahead + subs r2, r2, #32 ; count the bytes for this block + ldm r1!, {r4-r9, r12, lr} ; load 32 bytes + stm r3!, {r4-r9, r12, lr} ; store 32 bytes + bhs %B11 ; keep going until we're done +12 + ldm r1!, {r4-r9, r12, lr} ; load 32 bytes + stm r3!, {r4-r9, r12, lr} ; store 32 bytes +13 + adds r2, r2, #(32 - 8) ; recover original count, and pre-decrement + blo %F15 ; if not enough remaining, skip this loop +14 + subs r2, r2, #8 ; decrement count + ldrd r4, r5, [r1], #8 ; fetch pair of words + strd r4, r5, [r3], #8 ; store pair of words + bhs %B14 ; loop while we still have data remaining +15 + adds r2, r2, #8 ; recover final count + + EPILOG_POP {r4-r9, r11, lr} + EPILOG_NOP bne CpySmal ; if some left, continue with small + EPILOG_RETURN ; else just return + +; +; Source is not word-aligned; slow case +; + +20 + subs r2, r2, #64 ; pre-decrement to simplify the loop + blo %23 ; skip over the loop if we don't have enough + pld [r1, #32] ; pre-load one block ahead +21 + pld [r1, #64] ; prefetch ahead + ldr r4, [r1, #0] ; load 32 bytes + ldr r5, [r1, #4] ; + ldr r6, [r1, #8] ; + ldr r7, [r1, #12] ; + ldr r8, [r1, #16] ; + ldr r9, [r1, #20] ; + ldr r12, [r1, #24] ; + ldr lr, [r1, #28] ; + adds r1, r1, #32 ; update pointer + subs r2, r2, #32 ; count the bytes for this block + stm r3!, {r4-r9, r12, lr} ; store 32 bytes + bhs %B21 ; keep going until we're done +23 + adds r2, r2, #(64 - 8) ; recover original count, and pre-decrement + blo %F25 ; if not enough remaining, skip this loop +24 + ldr r4, [r1] ; fetch pair of words + ldr r5, [r1, #4] ; + adds r1, r1, #8 ; update pointer + subs r2, r2, #8 ; decrement count + strd r4, r5, [r3], #8 ; store pair of words + bhs %B24 ; loop while we still have data remaining +25 + adds r2, r2, #8 ; recover final count + + EPILOG_POP {r4-r9, r11, lr} + EPILOG_NOP bne CpySmal ; if some left, continue with small + EPILOG_RETURN ; else just return + + NESTED_END __memcpy_forward_large_integer + + +; +; __memcpy_forward_large_neon (internal calling convention) +; +; Copy large (>= 16 bytes) blocks of memory in a forward direction, +; using NEON registers. +; + +#if !defined(_BOOTCRT_) + + ALIGN 32 + NESTED_ENTRY __memcpy_forward_large_neon_wrapper + +__memcpy_forward_large_neon + + PROLOG_PUSH {r4-r5, r11, lr} + + subs r2, r2, #32 ; pre-decrement to simplify the loop + blo %F13 ; skip over the loop if we don't have enough + subs r2, r2, #32 ; pre-decrement to simplify the loop + pld [r1, #32] ; pre-load one block ahead + blo %F12 ; skip over the loop if we don't have enough +11 + pld [r1, #64] ; prefetch ahead + subs r2, r2, #32 ; count the bytes for this block + vld1.8 {d0-d3}, [r1]! ; load 32 bytes + vst1.8 {d0-d3}, [r3]! ; store 32 bytes + bhs %B11 ; keep going until we're done +12 + vld1.8 {d0-d3}, [r1]! ; load 32 bytes + vst1.8 {d0-d3}, [r3]! ; store 32 bytes +13 + adds r2, r2, #(32 - 8) ; recover original count, and pre-decrement + blo %F15 ; if not enough remaining, skip this loop +14 + ldr r4, [r1] ; fetch pair of words + ldr r5, [r1, #4] ; + adds r1, r1, #8 ; update pointer + str r4, [r3] ; store pair of words + str r5, [r3, #4] ; + adds r3, r3, #8 + subs r2, r2, #8 ; decrement count + bhs %B14 ; loop while we still have data remaining +15 + adds r2, r2, #8 ; recover final count + + EPILOG_POP {r4-r5, r11, lr} + EPILOG_NOP bne CpySmal ; if some left, continue with small + EPILOG_RETURN ; else just return + + NESTED_END __memcpy_forward_large_neon + +#endif + + +; +; void *memmove(void *dst, const void *src, size_t length) +; +; Copy a block of memory in a forward or reverse direction, ensuring that +; overlapping source/destination regions are copied correctly. +; + + ALIGN 32 + LEAF_ENTRY memmove + + subs r3, r0, r1 ; compute dest - source + cmp r3, r2 ; compare against size + bhs memcpy ; if no overlap, we can just do memcpy + + ALTERNATE_ENTRY __memcpy_reverse_new + + cmp r2, #16 ; less than 16 bytes? + pld [r1] ; preload the first cache line + bhs MovLrge ; go to the small copy case directly + +MovSmal tbb [pc, r2] ; branch to specialized bits for small copies +__SwitchTable1_Move +MTable dcb (Move0 - MTable) / 2 ; 0B + dcb (Move1 - MTable) / 2 ; 1B + dcb (Move2 - MTable) / 2 ; 2B + dcb (Move3 - MTable) / 2 ; 3B + dcb (Move4 - MTable) / 2 ; 4B + dcb (Move5 - MTable) / 2 ; 5B + dcb (Move6 - MTable) / 2 ; 6B + dcb (Move7 - MTable) / 2 ; 7B + dcb (Move8 - MTable) / 2 ; 8B + dcb (Move9 - MTable) / 2 ; 9B + dcb (Move10 - MTable) / 2 ; 10B + dcb (Move11 - MTable) / 2 ; 11B + dcb (Move12 - MTable) / 2 ; 12B + dcb (Move13 - MTable) / 2 ; 13B + dcb (Move14 - MTable) / 2 ; 14B + dcb (Move15 - MTable) / 2 ; 15B +__SwitchTableEnd_Move + +Move1 ldrb r2, [r1] + strb r2, [r0] +Move0 bx lr + +Move2 ldrh r2, [r1] + strh r2, [r0] + bx lr + +Move3 ldrh r2, [r1] + ldrb r1, [r1, #2] + strh r2, [r0] + strb r1, [r0, #2] + bx lr + +Move4 ldr r2, [r1] + str r2, [r0] + bx lr + +Move5 ldr r2, [r1] + ldrb r1, [r1, #4] + str r2, [r0] + strb r1, [r0, #4] + bx lr + +Move6 ldr r2, [r1] + ldrh r1, [r1, #4] + str r2, [r0] + strh r1, [r0, #4] + bx lr + +Move7 ldr r3, [r1] + ldrh r2, [r1, #4] + ldrb r1, [r1, #6] + str r3, [r0] + strh r2, [r0, #4] + strb r1, [r0, #6] + bx lr + +Move8 ldr r2, [r1] + ldr r1, [r1, #4] + str r2, [r0] + str r1, [r0, #4] + bx lr + +Move9 ldr r3, [r1] + ldr r2, [r1, #4] + ldrb r1, [r1, #8] + str r3, [r0] + str r2, [r0, #4] + strb r1, [r0, #8] + bx lr + +Move10 ldr r3, [r1] + ldr r2, [r1, #4] + ldrh r1, [r1, #8] + str r3, [r0] + str r2, [r0, #4] + strh r1, [r0, #8] + bx lr + +Move11 ldr r12, [r1] + ldr r3, [r1, #4] + ldrh r2, [r1, #8] + ldrb r1, [r1, #10] + str r12, [r0] + str r3, [r0, #4] + strh r2, [r0, #8] + strb r1, [r0, #10] + bx lr + +Move12 ldr r12, [r1] + ldr r2, [r1, #4] + ldr r1, [r1, #8] + str r12, [r0] + str r2, [r0, #4] + str r1, [r0, #8] + bx lr + +Move13 ldr r12, [r1] + ldr r3, [r1, #4] + ldr r2, [r1, #8] + ldrb r1, [r1, #12] + str r12, [r0] + str r3, [r0, #4] + str r2, [r0, #8] + strb r1, [r0, #12] + bx lr + +Move14 ldr r12, [r1] + ldr r3, [r1, #4] + ldr r2, [r1, #8] + ldrh r1, [r1, #12] + str r12, [r0] + str r3, [r0, #4] + str r2, [r0, #8] + strh r1, [r0, #12] + bx lr + +Move15 ldrh r3, [r1, #12] + ldrb r2, [r1, #14] + strh r3, [r0, #12] + strb r2, [r0, #14] + ldr r3, [r1] + ldr r2, [r1, #4] + ldr r1, [r1, #8] + str r3, [r0] + str r2, [r0, #4] + str r1, [r0, #8] + bx lr + +MovLrge + +#if defined(_BOOTCRT_) + + b __memcpy_reverse_large_integer ; always use integer in boot code + +#else + + eor r12, r0, r1 ; see if src/dst are equally aligned + tst r12, #3 ; at least to a 4 byte boundary + bne __memcpy_reverse_large_neon ; if not, always use NEON + mov32 r12, __memcpy_reverse_large_func + ldr pc, [r12] + +#endif + + LEAF_END memmove + + +; +; __memcpy_reverse_large_integer (internal calling convention) +; +; Copy large (>= 16 bytes) block of memory in a reverse direction, +; using NEON registers. +; + + ALIGN 32 + NESTED_ENTRY __memcpy_reverse_large_integer_wrapper + +__memcpy_reverse_large_integer + + PROLOG_NOP adds r3, r0, r2 ; advance destination to end + PROLOG_NOP adds r1, r1, r2 ; advance source to end + PROLOG_NOP lsls r12, r3, #31 ; C = bit 1, N = bit 0 + PROLOG_NOP pld [r1, #-32] ; pre-load one block ahead + PROLOG_PUSH {r4-r9, r11, lr} + +; +; Align destination to a word boundary +; + + bpl %F1 + ldrb r4, [r1, #-1]! ; fetch byte + subs r2, r2, #1 ; decrement count + strb r4, [r3, #-1]! ; store byte + lsls r12, r3, #31 ; compute updated status +1 + bcc %F2 ; if already aligned, just skip ahead + ldrh r4, [r1, #-2]! ; fetch halfword + subs r2, r2, #2 ; decrement count + strh r4, [r3, #-2]! ; store halfword +2 + tst r1, #3 ; is the source now word-aligned? + bne %F20 ; if not, we have to use the slow path + +; +; Source is word-aligned; fast case +; + +10 + subs r2, r2, #32 ; pre-decrement to simplify the loop + blo %F13 ; skip over the loop if we don't have enough + subs r2, r2, #32 ; pre-decrement to simplify the loop + pld [r1, #-64] ; pre-load one block ahead + blo %F12 ; skip over the loop if we don't have enough +11 + pld [r1, #-96] ; prefetch ahead + subs r2, r2, #32 ; count the bytes for this block + ldmdb r1!, {r4-r9, r12, lr} ; load 32 bytes + stmdb r3!, {r4-r9, r12, lr} ; store 32 bytes + bhs %B11 ; keep going until we're done +12 + ldmdb r1!, {r4-r9, r12, lr} ; load 32 bytes + stmdb r3!, {r4-r9, r12, lr} ; store 32 bytes +13 + adds r2, r2, #(32 - 8) ; recover original count, and pre-decrement + blo %F15 ; if not enough remaining, skip this loop +14 + subs r2, r2, #8 ; decrement count + ldrd r4, r5, [r1, #-8]! ; fetch pair of words + strd r4, r5, [r3, #-8]! ; store pair of words + bhs %B14 ; loop while we still have data remaining +15 + adds r2, r2, #8 ; determine final count + subs r1, r1, r2 ; recover original source + + EPILOG_POP {r4-r9, r11, lr} + EPILOG_NOP bne MovSmal ; if some left, continue with small + EPILOG_RETURN ; else just return + + +; +; Source is not word-aligned; slow case +; + +20 + subs r2, r2, #64 ; pre-decrement to simplify the loop + blo %F23 ; skip over the loop if we don't have enough + pld [r1, #-64] ; pre-load one block ahead +21 + pld [r1, #-96] ; prefetch ahead + subs r2, r2, #32 ; count the bytes for this block + ldr r4, [r1, #-32]! ; load 32 bytes + ldr r5, [r1, #4] ; + ldr r6, [r1, #8] ; + ldr r7, [r1, #12] ; + ldr r8, [r1, #16] ; + ldr r9, [r1, #20] ; + ldr r12, [r1, #24] ; + ldr lr, [r1, #28] ; + stmdb r3!, {r4-r9, r12, lr} ; store 32 bytes + bhs %B21 ; keep going until we're done +23 + adds r2, r2, #(64 - 8) ; recover original count, and pre-decrement + blo %F25 ; if not enough remaining, skip this loop +24 + subs r2, r2, #8 ; decrement count + ldr r4, [r1, #-8]! ; fetch pair of words + ldr r5, [r1, #4] ; + strd r4, r5, [r3, #-8]! ; store pair of words + bhs %B24 ; loop while we still have data remaining +25 + adds r2, r2, #8 ; determine final count + subs r1, r1, r2 ; recover original source + + EPILOG_POP {r4-r9, r11, lr} + EPILOG_NOP bne MovSmal ; if some left, continue with small + EPILOG_RETURN ; else just return + + NESTED_END __memcpy_reverse_large_integer + + +; +; __memcpy_reverse_large_neon (internal calling convention) +; +; Copy large (>= 16 bytes) block of memory in a reverse direction, +; using NEON registers. +; + +#if !defined(_BOOTCRT_) + + ALIGN 32 + NESTED_ENTRY __memcpy_reverse_large_neon_wrapper + +__memcpy_reverse_large_neon + + PROLOG_NOP adds r3, r0, r2 ; advance destination to end + PROLOG_NOP adds r1, r1, r2 ; advance source to end + PROLOG_NOP lsls r12, r3, #31 ; C = bit 1, N = bit 0 + PROLOG_NOP pld [r1, #-32] ; pre-load one block ahead + PROLOG_PUSH {r4-r5, r11, lr} + +; +; Align destination to a word boundary +; + + bpl %F1 + ldrb r4, [r1, #-1]! ; fetch byte + subs r2, r2, #1 ; decrement count + strb r4, [r3, #-1]! ; store byte + lsls r12, r3, #31 ; compute updated status +1 + bcc %F2 ; if already aligned, just skip ahead + ldrh r4, [r1, #-2]! ; fetch halfword + subs r2, r2, #2 ; decrement count + strh r4, [r3, #-2]! ; store halfword +2 + +; +; Perform main copy +; + + subs r2, r2, #32 ; pre-decrement to simplify the loop + blo %F13 ; skip over the loop if we don't have enough + subs r2, r2, #32 ; pre-decrement to simplify the loop + pld [r1, #-64] ; pre-load one block ahead + blo %F12 ; skip over the loop if we don't have enough +11 + pld [r1, #-96] ; prefetch ahead + subs r1, r1, #32 + subs r3, r3, #32 + subs r2, r2, #32 ; count the bytes for this block + vld1.8 {d0-d3}, [r1] ; load 32 bytes + vst1.8 {d0-d3}, [r3] ; store 32 bytes + bhs %B11 ; keep going until we're done +12 + subs r1, r1, #32 + subs r3, r3, #32 + vld1.8 {d0-d3}, [r1] ; load 32 bytes + vst1.8 {d0-d3}, [r3] ; store 32 bytes +13 + adds r2, r2, #(32 - 8) ; recover original count, and pre-decrement + blo %F15 ; if not enough remaining, skip this loop +14 + ldr r4, [r1, #-8]! ; fetch pair of words + ldr r5, [r1, #4] ; fetch pair of words + subs r2, r2, #8 ; decrement count + str r4, [r3, #-8]! ; store pair of words + str r5, [r3, #4] + bhs %B14 ; loop while we still have data remaining +15 + adds r2, r2, #8 ; determine final count + subs r1, r1, r2 ; recover original source + + EPILOG_POP {r4-r5, r11, lr} + EPILOG_NOP bne MovSmal ; if some left, continue with small + EPILOG_RETURN ; else just return + + NESTED_END __memcpy_reverse_large_neon + +#endif + + +; +; __memcpy_decide (internal calling convention) +; +; Determine whether to use integer or NEON for future memcpy's. +; + +#if !defined(_BOOTCRT_) + + ALIGN 32 + NESTED_ENTRY __memcpy_decide_wrapper + +__memcpy_decide + + PROLOG_PUSH {r4-r5, r11, lr} + + ; + ; We want to use integer memcpy's on the A9, which has an external cache. + ; + ; First determine if we're in user or kernel mode. Reading CPSR + ; from user mode will either return the proper 5 mode bits, or all 0s. + ; Conveniently, user mode is 0x10, and there is no mode 0x00, so if + ; we read CPSR and the low 4 bits are 0, that's good enough. + ; + + mrs r4, cpsr ; get CPSR + ands r4, r4, #0xf ; isolate the low 4 bits of the mode + beq %F1 ; if 0, we're in user mode + + ; + ; If we are in kernel mode, read the MIDR directly. + ; + + CP_READ r4, CP15_MIDR ; read main ID register + ubfx r5, r4, #24, #8 ; get implementer + lsrs r4, r4, #4 ; shift off revision field + cmp r5, #0x41 ; is implementer == ARM? + bne %F3 ; if not, use NEON + bfc r4, #12, #20 ; clear upper bits + ldr r5, =0xc09 ; A9 signature + cmp r4, r5 ; is this an A9? + bne %F3 ; if not, use NEON + b %F2 ; otherwise, use integer + + ; + ; If we are in user mode, check the "external cache available" flag + ; +1 + ldr r4, =MM_SHARED_USER_DATA_VA + UsProcessorFeatures + PF_ARM_EXTERNAL_CACHE_AVAILABLE + ldrb r4, [r4] ; get external cache bit + cbz r4, %F3 ; if no external cache, do NEON + + ; + ; Register for integer functions + ; +2 + ldr r4, =__memcpy_forward_large_integer ; select integer functions + ldr r5, =__memcpy_forward_large_func ; + str r4, [r5] ; + ldr r4, =__memcpy_reverse_large_integer ; select integer functions + ldr r5, =__memcpy_reverse_large_func ; + str r4, [r5] ; + b %F4 + + ; + ; Register for NEON functions + ; +3 + ldr r4, =__memcpy_forward_large_neon ; select NEON functions + ldr r5, =__memcpy_forward_large_func ; + str r4, [r5] ; + ldr r4, =__memcpy_reverse_large_neon ; select NEON functions + ldr r5, =__memcpy_reverse_large_func ; + str r4, [r5] ; +4 + EPILOG_POP {r4-r5, r11, lr} ; restore saved registers + EPILOG_NOP ldr pc, [r12] ; jump to the appropriate target + + NESTED_END __memcpy_decide + +#endif + + +; +; void _memcpy_strict_align(void *dst, const void *src, size_t length) +; +; Copy a block of memory in a forward direction, only performing naturally-aligned +; accesses. +; + + ALIGN 32 + LEAF_ENTRY _memcpy_strict_align + +; +; Verify alignment between source and destination +; + + sub r3, r0, r1 ; get relative alignment of source and destination + cbz r2, CopyExit ; exit if 0 count + ands r3, r3, #3 ; check DWORD alignment + bne CopyMisalignedHalf ; misaligned + +; +; Source and destination are equally aligned: just align the +; destination and the source will end up aligned as well +; + + tst r0, #3 ; dword aligned at the dest? + beq WordAligned_0 ; if so, skip ahead + tst r0, #1 ; halfword aligned at the dest? + beq HalfAligned_0 ; if so, skip ahead + + subs r2, r2, #1 ; decrement count + ldrb r3, [r1], #1 ; fetch byte + strb r3, [r0], #1 ; store it + beq CopyExit ; stop if done + tst r0, #3 ; word aligned now? + beq WordAligned_0 ; if so, skip ahead + +HalfAligned_0 + cmp r2, #2 ; do we have at least 2 bytes left? + blo CopyFinalBytes ; if not, copy bytes + subs r2, r2, #2 ; decrement count + ldrh r3, [r1], #2 ; fetch halfword + strh r3, [r0], #2 ; store it + beq CopyExit ; stop if done + +WordAligned_0 + subs r2, r2, #4 ; at least 4 bytes remaining? + blt WordLoopEnd_0 ; if not, skip the main loop +WordLoop_0 + subs r2, r2, #4 ; decrement count + ldr r3, [r1], #4 ; fetch word + str r3, [r0], #4 ; store it + bge WordLoop_0 ; stop if done +WordLoopEnd_0 + adds r2, r2, #4 ; recover the extra 4 we subtracted + beq CopyExit ; stop if that's everything + +CopyFinalHalfwords + subs r2, r2, #2 ; at least 2 bytes remaining? + blt CopyFinalHalfwordsEnd ; if not, skip this +CopyFinalHalfwordsLoop + subs r2, r2, #2 ; decrement count + ldrh r3, [r1], #2 ; fetch halfword + strh r3, [r0], #2 ; store it + bge CopyFinalHalfwordsLoop ; loop until done +CopyFinalHalfwordsEnd + adds r2, r2, #2 ; recover the extra 2 we subtracted + beq CopyExit ; stop if that's everything + +CopyFinalBytes + subs r2, r2, #1 ; decrement count + ldrb r3, [r1], #1 ; fetch byte + strb r3, [r0], #1 ; store it + bne CopyFinalBytes ; loop until done +CopyExit + bx lr ; return + + +; +; Source and destination are misaligned by 2 bytes +; + +CopyMisalignedHalf + cmp r3, #2 ; misaligned by a halfword? + bne CopyMisalignedByte ; if not, skip + + tst r0, #3 ; dword aligned at the dest? + beq WordAligned_2 ; if so, skip ahead + tst r0, #1 ; halfword aligned at the dest? + beq HalfAligned_2 ; if so, skip ahead + + subs r2, r2, #1 ; decrement count + ldrb r3, [r1], #1 ; fetch byte + strb r3, [r0], #1 ; store it + beq CopyExit ; stop if done + tst r0, #3 ; word aligned now? + beq WordAligned_2 ; if so, skip ahead + +HalfAligned_2 + cmp r2, #2 ; do we have at least 2 bytes left? + blo CopyFinalBytes ; if not, copy bytes + subs r2, r2, #2 ; decrement count + ldrh r3, [r1], #2 ; fetch halfword + strh r3, [r0], #2 ; store it + beq CopyExit ; stop if done + +WordAligned_2 + subs r2, r2, #6 ; at least 6 bytes remaining? + blt WordLoopEnd_2 ; if so, skip the main loop + ldrh r12, [r1], #2 ; preload a halfword of source + subs r2, r2, #2 ; count these 2 bytes +WordLoop_2 + subs r2, r2, #4 ; decrement count + ldr r3, [r1], #4 ; fetch word + orr r12, r12, r3, lsl #16 ; copy low 16 bits to upper 16 of r12 + str r12, [r0], #4 ; store it + lsr r12, r3, #16 ; copy upper 16 bits to lower 16 of r12 + bge WordLoop_2 ; stop if done + strh r12, [r0], #2 ; store the extra halfword to the dest +WordLoopEnd_2 + adds r2, r2, #6 ; recover the extra 6 we subtracted + beq CopyExit ; stop if that's everything + b CopyFinalHalfwords ; otherwise, copy remainder + + +; +; Source and destination are misaligned by 1 byte +; + +CopyMisalignedByte + cmp r3, #1 ; misaligned by a byte? + bne CopyMisalignedByte3 ; if not, skip + + tst r0, #3 ; dword aligned at the dest? + beq WordAligned_1 ; if so, skip ahead +ByteAlign_1 + subs r2, r2, #1 ; decrement count + ldrb r3, [r1], #1 ; fetch byte + strb r3, [r0], #1 ; store it + beq CopyExit ; stop if done + tst r0, #3 ; word aligned now? + bne ByteAlign_1 ; if not, keep copying bytes + +WordAligned_1 + subs r2, r2, #5 ; at least 5 bytes remaining? + blt WordLoopEnd_1 ; if so, skip the main loop + ldrb r12, [r1], #1 ; preload a byte of source + subs r2, r2, #1 ; count this byte +WordLoop_1 + subs r2, r2, #4 ; decrement count + ldr r3, [r1], #4 ; fetch word + orr r12, r12, r3, lsl #8 ; copy low 24 bits to upper 24 of r12 + str r12, [r0], #4 ; store it + lsr r12, r3, #24 ; copy upper 8 bits to lower 8 of r12 + bge WordLoop_1 ; stop if done + strb r12, [r0], #1 ; store the extra byte to the dest +WordLoopEnd_1 + adds r2, r2, #5 ; recover the extra 5 we subtracted + beq CopyExit ; stop if that's everything + b CopyFinalBytes ; otherwise, copy remainder + + +; +; Source and destination are misaligned by 3 bytes +; + +CopyMisalignedByte3 + tst r0, #3 ; dword aligned at the dest? + beq WordAligned_3 ; if so, skip ahead +ByteAlign_3 + subs r2, r2, #1 ; decrement count + ldrb r3, [r1], #1 ; fetch byte + strb r3, [r0], #1 ; store it + beq CopyExit ; stop if done + tst r0, #3 ; word aligned now? + bne ByteAlign_3 ; if not, keep copying bytes + +WordAligned_3 + subs r2, r2, #7 ; at least 7 bytes remaining? + blt WordLoopEnd_3 ; if so, skip the main loop + ldrb r12, [r1], #1 ; preload a byte of source + ldrh r3, [r1], #2 ; preload a halfword of source + orr r12, r12, r3, lsl #8 ; OR in the halfword + subs r2, r2, #3 ; count these 3 bytes +WordLoop_3 + subs r2, r2, #4 ; decrement count + ldr r3, [r1], #4 ; fetch word + orr r12, r12, r3, lsl #24 ; copy low 8 bits to upper 8 of r12 + str r12, [r0], #4 ; store it + lsr r12, r3, #8 ; copy upper 24 bits to lower 24 of r12 + bge WordLoop_3 ; stop if done + strh r12, [r0], #2 ; store the extra halfword to the dest + lsr r12, r12, #16 ; down to the final byte + strb r12, [r0], #1 ; store the extra byte to the dest +WordLoopEnd_3 + adds r2, r2, #7 ; recover the extra 7 we subtracted + beq CopyExit ; stop if that's everything + b CopyFinalBytes ; otherwise, copy remainder + + LEAF_END _memcpy_strict_align + + END diff --git a/src/vm/arm/patchedcode.S b/src/vm/arm/patchedcode.S new file mode 100644 index 0000000000..9335fe65fc --- /dev/null +++ b/src/vm/arm/patchedcode.S @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== +#include "unixasmmacros.inc" +#include "asmconstants.h" + +.syntax unified +.thumb + +// ------------------------------------------------------------------ +// Start of the writeable code region + LEAF_ENTRY JIT_PatchedCodeStart, _TEXT + bx lr + LEAF_END JIT_PatchedCodeStart, _TEXT + +// ------------------------------------------------------------------ +// Optimized TLS getters + + LEAF_ENTRY GetTLSDummy, _TEXT + mov r0, #0 + bx lr + LEAF_END GetTLSDummy, _TEXT + + .align 4 + LEAF_ENTRY ClrFlsGetBlock, _TEXT + // This will be overwritten at runtime with optimized ClrFlsGetBlock implementation + b C_FUNC(GetTLSDummy) + // Just allocate space that will be filled in at runtime + .space (TLS_GETTER_MAX_SIZE_ASM - 2) + LEAF_END ClrFlsGetBlock, _TEXT + +// ------------------------------------------------------------------ +// GC write barrier support. +// +// GC Write barriers are defined in asmhelpers.asm. The following functions are used to define +// patchable location where the write-barriers are copied over at runtime + + LEAF_ENTRY JIT_PatchedWriteBarrierStart, _TEXT + LEAF_END JIT_PatchedWriteBarrierStart, _TEXT + + // These write barriers are overwritten on the fly + // See ValidateWriteBarriers on how the sizes of these should be calculated + .align 4 + LEAF_ENTRY JIT_WriteBarrier, _TEXT + .space (0x84) + LEAF_END_MARKED JIT_WriteBarrier, _TEXT + + .align 4 + LEAF_ENTRY JIT_CheckedWriteBarrier, _TEXT + .space (0x9C) + LEAF_END_MARKED JIT_CheckedWriteBarrier, _TEXT + + .align 4 + LEAF_ENTRY JIT_ByRefWriteBarrier, _TEXT + .space (0xA0) + LEAF_END_MARKED JIT_ByRefWriteBarrier , _TEXT + + LEAF_ENTRY JIT_PatchedWriteBarrierLast, _TEXT + LEAF_END JIT_PatchedWriteBarrierLast, _TEXT + +// ------------------------------------------------------------------ +// End of the writeable code region + LEAF_ENTRY JIT_PatchedCodeLast, _TEXT + bx lr + LEAF_END JIT_PatchedCodeLast, _TEXT diff --git a/src/vm/arm/patchedcode.asm b/src/vm/arm/patchedcode.asm new file mode 100644 index 0000000000..2ef175ea56 --- /dev/null +++ b/src/vm/arm/patchedcode.asm @@ -0,0 +1,606 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +;; ==++== +;; + +;; +;; ==--== +#include "ksarm.h" + +#include "asmconstants.h" + +#include "asmmacros.h" + + SETALIAS JIT_Box,?JIT_Box@@YAPAVObject@@PAUCORINFO_CLASS_STRUCT_@@PAX@Z + SETALIAS JIT_New, ?JIT_New@@YAPAVObject@@PAUCORINFO_CLASS_STRUCT_@@@Z + SETALIAS JIT_Box, ?JIT_Box@@YAPAVObject@@PAUCORINFO_CLASS_STRUCT_@@PAX@Z + SETALIAS FramedAllocateString, ?FramedAllocateString@@YAPAVStringObject@@K@Z + SETALIAS g_pStringClass, ?g_pStringClass@@3PAVMethodTable@@A + SETALIAS JIT_NewArr1, ?JIT_NewArr1@@YAPAVObject@@PAUCORINFO_CLASS_STRUCT_@@H@Z + SETALIAS CopyValueClassUnchecked, ?CopyValueClassUnchecked@@YAXPAX0PAVMethodTable@@@Z + + IMPORT $JIT_New + IMPORT $JIT_Box + IMPORT $FramedAllocateString + IMPORT $g_pStringClass + IMPORT $JIT_NewArr1 + IMPORT $CopyValueClassUnchecked + IMPORT SetAppDomainInObject + + + IMPORT JIT_GetSharedNonGCStaticBase_Helper + IMPORT JIT_GetSharedGCStaticBase_Helper + + + EXPORT JIT_TrialAllocSFastMP_InlineGetThread__PatchTLSOffset + EXPORT JIT_BoxFastMP_InlineGetThread__PatchTLSOffset + EXPORT AllocateStringFastMP_InlineGetThread__PatchTLSOffset + EXPORT JIT_NewArr1VC_MP_InlineGetThread__PatchTLSOffset + EXPORT JIT_NewArr1OBJ_MP_InlineGetThread__PatchTLSOffset + + EXPORT JIT_GetSharedNonGCStaticBase__PatchTLSLabel + EXPORT JIT_GetSharedNonGCStaticBaseNoCtor__PatchTLSLabel + EXPORT JIT_GetSharedGCStaticBase__PatchTLSLabel + EXPORT JIT_GetSharedGCStaticBaseNoCtor__PatchTLSLabel + + MACRO + PATCHABLE_INLINE_GETTHREAD $reg, $label +$label + mrc p15, 0, $reg, c13, c0, 2 + ldr $reg, [$reg, #0xe10] + MEND + + + MACRO + PATCHABLE_INLINE_GETAPPDOMAIN $reg, $label +$label + mrc p15, 0, $reg, c13, c0, 2 + ldr $reg, [$reg, #0xe10] + MEND + + TEXTAREA + + + MACRO + FIX_INDIRECTION $Reg, $label +#ifdef FEATURE_PREJIT + tst $Reg, #1 + beq $label + ldr $Reg, [$Reg, #-1] +$label +#endif + MEND + + +; ------------------------------------------------------------------ +; Start of the writeable code region + LEAF_ENTRY JIT_PatchedCodeStart + bx lr + LEAF_END + +; ------------------------------------------------------------------ +; Optimized TLS getters + + ALIGN 4 + LEAF_ENTRY GetThread + ; This will be overwritten at runtime with optimized GetThread implementation + b GetTLSDummy + ; Just allocate space that will be filled in at runtime + SPACE (TLS_GETTER_MAX_SIZE_ASM - 2) + LEAF_END + + ALIGN 4 + LEAF_ENTRY GetAppDomain + ; This will be overwritten at runtime with optimized GetThread implementation + b GetTLSDummy + ; Just allocate space that will be filled in at runtime + SPACE (TLS_GETTER_MAX_SIZE_ASM - 2) + LEAF_END + + LEAF_ENTRY GetTLSDummy + mov r0, #0 + bx lr + LEAF_END + + ALIGN 4 + LEAF_ENTRY ClrFlsGetBlock + ; This will be overwritten at runtime with optimized ClrFlsGetBlock implementation + b GetTLSDummy + ; Just allocate space that will be filled in at runtime + SPACE (TLS_GETTER_MAX_SIZE_ASM - 2) + LEAF_END + +; ------------------------------------------------------------------ +; GC write barrier support. +; +; GC Write barriers are defined in asmhelpers.asm. The following functions are used to define +; patchable location where the write-barriers are copied over at runtime + + LEAF_ENTRY JIT_PatchedWriteBarrierStart + ; Cannot be empty function to prevent LNK1223 + bx lr + LEAF_END + + ; These write barriers are overwritten on the fly + ; See ValidateWriteBarriers on how the sizes of these should be calculated + ALIGN 4 + LEAF_ENTRY JIT_WriteBarrier + SPACE (0x84) + LEAF_END_MARKED JIT_WriteBarrier + + ALIGN 4 + LEAF_ENTRY JIT_CheckedWriteBarrier + SPACE (0x9C) + LEAF_END_MARKED JIT_CheckedWriteBarrier + + ALIGN 4 + LEAF_ENTRY JIT_ByRefWriteBarrier + SPACE (0xA0) + LEAF_END_MARKED JIT_ByRefWriteBarrier + + LEAF_ENTRY JIT_PatchedWriteBarrierLast + ; Cannot be empty function to prevent LNK1223 + bx lr + LEAF_END + +; JIT Allocation helpers when TLS Index for Thread is low enough for fast helpers + +;--------------------------------------------------------------------------- +; IN: r0: MethodTable* +;; OUT: r0: new object + + LEAF_ENTRY JIT_TrialAllocSFastMP_InlineGetThread + + ;get object size + ldr r1, [r0, #MethodTable__m_BaseSize] + + ; m_BaseSize is guaranteed to be a multiple of 4. + + ;getThread + PATCHABLE_INLINE_GETTHREAD r12, JIT_TrialAllocSFastMP_InlineGetThread__PatchTLSOffset + + ;load current allocation pointers + ldr r2, [r12, #Thread__m_alloc_context__alloc_limit] + ldr r3, [r12, #Thread__m_alloc_context__alloc_ptr] + + ;add object size to current pointer + add r1, r3 + + ;if beyond the limit call c++ method + cmp r1, r2 + bhi AllocFailed + + ;r1 is the new alloc_ptr and r3 has object address + ;update the alloc_ptr in Thread + str r1, [r12, #Thread__m_alloc_context__alloc_ptr] + + ;write methodTable in object + str r0, [r3] + + ;return object in r0 + mov r0, r3 + +#ifdef _DEBUG + ; Tail call to a helper that will set the current AppDomain index into the object header and then + ; return the object pointer back to our original caller. + b SetAppDomainInObject +#else + ;return + bx lr +#endif + +AllocFailed + b $JIT_New + LEAF_END + + +;--------------------------------------------------------------------------- +; HCIMPL2(Object*, JIT_Box, CORINFO_CLASS_HANDLE type, void* unboxedData) +; IN: r0: MethodTable* +; IN: r1: data pointer +;; OUT: r0: new object + + LEAF_ENTRY JIT_BoxFastMP_InlineGetThread + + ldr r2, [r0, #MethodTable__m_pWriteableData] + + ;Check whether the class has been initialized + ldr r2, [r2, #MethodTableWriteableData__m_dwFlags] + cmp r2, #MethodTableWriteableData__enum_flag_Unrestored + bne ClassNotInited + + ; Check whether the object contains pointers + ldr r3, [r0, #MethodTable__m_dwFlags] + cmp r3, #MethodTable__enum_flag_ContainsPointers + bne ContainsPointers + + ldr r2, [r0, #MethodTable__m_BaseSize] + + ;m_BaseSize is guranteed to be a multiple of 4 + + ;GetThread + PATCHABLE_INLINE_GETTHREAD r12, JIT_BoxFastMP_InlineGetThread__PatchTLSOffset + + ldr r3, [r12, #Thread__m_alloc_context__alloc_ptr] + add r3, r2 + + ldr r2, [r12, #Thread__m_alloc_context__alloc_limit] + + cmp r3, r2 + bhi AllocFailed2 + + ldr r2, [r12, #Thread__m_alloc_context__alloc_ptr] + + ;advance alloc_ptr in Thread + str r3, [r12, #Thread__m_alloc_context__alloc_ptr] + + ;write methodtable* in the object + str r0, [r2] + + ;copy the contents of value type in the object + + ldr r3, [r0, #MethodTable__m_BaseSize] + sub r3, #0xc + + ;r3 = no of bytes to copy + + ;move address of object to return register + mov r0, r2 + + ;advance r2 to skip methodtable location + add r2, #4 + +CopyLoop + ldr r12, [r1, r3] + str r12, [r2, r3] + sub r3, #4 + bne CopyLoop + +#ifdef _DEBUG + ; Tail call to a helper that will set the current AppDomain index into the object header and then + ; return the object pointer back to our original caller. + b SetAppDomainInObject +#else + ;return + bx lr +#endif + +ContainsPointers +ClassNotInited +AllocFailed2 + b $JIT_Box + LEAF_END + + +;--------------------------------------------------------------------------- +; IN: r0: number of characters to allocate +;; OUT: r0: address of newly allocated string + + LEAF_ENTRY AllocateStringFastMP_InlineGetThread + + ; Instead of doing elaborate overflow checks, we just limit the number of elements to + ; MAX_FAST_ALLOCATE_STRING_SIZE. This is picked (in asmconstants.h) to avoid any possibility of + ; overflow and to ensure we never try to allocate anything here that really should go on the large + ; object heap instead. Additionally the size has been selected so that it will encode into an + ; immediate in a single cmp instruction. + + cmp r0, #MAX_FAST_ALLOCATE_STRING_SIZE + bhs OversizedString + + ; Calculate total string size: Align(base size + (characters * 2), 4). + mov r1, #(SIZEOF__BaseStringObject + 3) ; r1 == string base size + 3 for alignment round up + add r1, r1, r0, lsl #1 ; r1 += characters * 2 + bic r1, r1, #3 ; r1 &= ~3; round size to multiple of 4 + + ;GetThread + PATCHABLE_INLINE_GETTHREAD r12, AllocateStringFastMP_InlineGetThread__PatchTLSOffset + ldr r2, [r12, #Thread__m_alloc_context__alloc_limit] + ldr r3, [r12, #Thread__m_alloc_context__alloc_ptr] + + add r1, r3 + cmp r1, r2 + bhi AllocFailed3 + + ;can allocate + + ;advance alloc_ptr + str r1, [r12, #Thread__m_alloc_context__alloc_ptr] + + ; Write MethodTable pointer into new object. + ldr r1, =$g_pStringClass + ldr r1, [r1] + str r1, [r3] + + ; Write string length into new object. + str r0, [r3, #StringObject__m_StringLength] + + ;prepare to return new object address + mov r0, r3 + +#ifdef _DEBUG + ; Tail call to a helper that will set the current AppDomain index into the object header and then + ; return the object pointer back to our original caller. + b SetAppDomainInObject +#else + ;return + bx lr +#endif + + +OversizedString +AllocFailed3 + b $FramedAllocateString + + LEAF_END + + +; HCIMPL2(Object*, JIT_NewArr1, CORINFO_CLASS_HANDLE arrayTypeHnd_, INT_PTR size) +;--------------------------------------------------------------------------- +; IN: r0: type descriptor which contains the (shared) array method table and the element type. +; IN: r1: number of array elements +;; OUT: r0: address of newly allocated string + + LEAF_ENTRY JIT_NewArr1VC_MP_InlineGetThread + + ; Do a conservative check here for number of elements. + ; This is to avoid overflow while doing the calculations. We don't + ; have to worry about "large" objects, since the allocation quantum is never big enough for + ; LARGE_OBJECT_SIZE. + + ; For Value Classes, this needs to be < (max_value_in_4byte - size_of_base_array)/(max_size_of_each_element) + ; This evaluates to (2^32-1 - 0xc)/2^16 + + ; Additionally the constant has been chosen such that it can be encoded in a + ; single Thumb2 CMP instruction. + + cmp r1, #MAX_FAST_ALLOCATE_ARRAY_VC_SIZE + bhs OverSizedArray3 + + ;load MethodTable from ArrayTypeDesc + ldr r3, [r0, #ArrayTypeDesc__m_TemplateMT - 2] + + FIX_INDIRECTION r3, label1 + + ;get element size - stored in low 16bits of m_dwFlags + ldrh r12, [r3, #MethodTable__m_dwFlags] + + ; getting size of object to allocate + + ; multiply number of elements with size of each element + mul r2, r12, r1 + + ; add the base array size and 3 to align total bytes at 4 byte boundary + add r2, r2, #SIZEOF__ArrayOfValueType + 3 + bic r2, #3 + + ;GetThread + PATCHABLE_INLINE_GETTHREAD r12, JIT_NewArr1VC_MP_InlineGetThread__PatchTLSOffset + ldr r3, [r12, #Thread__m_alloc_context__alloc_ptr] + + add r3, r2 + + ldr r2, [r12, #Thread__m_alloc_context__alloc_limit] + + cmp r3, r2 + bhi AllocFailed6 + + ; can allocate + + ;r2 = address of new object + ldr r2, [r12, #Thread__m_alloc_context__alloc_ptr] + + ;update pointer in allocation context + str r3, [r12, #Thread__m_alloc_context__alloc_ptr] + + ;store number of elements + str r1, [r2, #ArrayBase__m_NumComponents] + + ;store methodtable + ldr r3, [r0, #ArrayTypeDesc__m_TemplateMT - 2] + + FIX_INDIRECTION r3, label2 + + str r3, [r2] + + ;copy return value + mov r0, r2 + +#ifdef _DEBUG + ; Tail call to a helper that will set the current AppDomain index into the object header and then + ; return the object pointer back to our original caller. + b SetAppDomainInObject +#else + ;return + bx lr +#endif + + + +AllocFailed6 +OverSizedArray3 + b $JIT_NewArr1 + + LEAF_END + + + +; HCIMPL2(Object*, JIT_NewArr1, CORINFO_CLASS_HANDLE arrayTypeHnd_, INT_PTR size) +;--------------------------------------------------------------------------- +; IN: r0: type descriptor which contains the (shared) array method table and the element type. +; IN: r1: number of array elements +;; OUT: r0: address of newly allocated string + + LEAF_ENTRY JIT_NewArr1OBJ_MP_InlineGetThread + + cmp r1, #MAX_FAST_ALLOCATE_ARRAY_OBJECTREF_SIZE + bhs OverSizedArray + + mov r2, #SIZEOF__ArrayOfObjectRef + add r2, r2, r1, lsl #2 + + ;r2 will be a multiple of 4 + + + ;GetThread + PATCHABLE_INLINE_GETTHREAD r12, JIT_NewArr1OBJ_MP_InlineGetThread__PatchTLSOffset + ldr r3, [r12, #Thread__m_alloc_context__alloc_ptr] + + add r3, r2 + + ldr r2, [r12, #Thread__m_alloc_context__alloc_limit] + + cmp r3, r2 + bhi AllocFailed4 + + ;can allocate + + ;r2 = address of new object + ldr r2, [r12, #Thread__m_alloc_context__alloc_ptr] + + ;update pointer in allocation context + str r3, [r12, #Thread__m_alloc_context__alloc_ptr] + + ;store number of elements + str r1, [r2, #ArrayBase__m_NumComponents] + + ;store methodtable + ldr r3, [r0, #ArrayTypeDesc__m_TemplateMT - 2] + + FIX_INDIRECTION r3, label3 + + str r3, [r2] + + ;copy return value + mov r0, r2 + +#ifdef _DEBUG + ; Tail call to a helper that will set the current AppDomain index into the object header and then + ; return the object pointer back to our original caller. + b SetAppDomainInObject +#else + ;return + bx lr +#endif + +OverSizedArray +AllocFailed4 + b $JIT_NewArr1 + LEAF_END + +; +; JIT Static access helpers when TLS Index for AppDomain is low enough for fast helpers +; + +; ------------------------------------------------------------------ +; void* JIT_GetSharedNonGCStaticBase(SIZE_T moduleDomainID, DWORD dwClassDomainID) + + LEAF_ENTRY JIT_GetSharedNonGCStaticBase_InlineGetAppDomain + ; Check if r0 (moduleDomainID) is not a moduleID + tst r0, #1 + beq HaveLocalModule1 + + PATCHABLE_INLINE_GETAPPDOMAIN r2, JIT_GetSharedNonGCStaticBase__PatchTLSLabel + + ; Get the LocalModule, r0 will always be odd, so: r0 * 2 - 2 <=> (r0 >> 1) * 4 + ldr r2, [r2 , #AppDomain__m_sDomainLocalBlock + DomainLocalBlock__m_pModuleSlots] + add r2, r2, r0, LSL #1 + ldr r0, [r2, #-2] + +HaveLocalModule1 + ; If class is not initialized, bail to C++ helper + add r2, r0, #DomainLocalModule__m_pDataBlob + ldrb r2, [r2, r1] + tst r2, #1 + beq CallHelper1 + + bx lr + +CallHelper1 + ; Tail call JIT_GetSharedNonGCStaticBase_Helper + b JIT_GetSharedNonGCStaticBase_Helper + LEAF_END + + +; ------------------------------------------------------------------ +; void* JIT_GetSharedNonGCStaticBaseNoCtor(SIZE_T moduleDomainID, DWORD dwClassDomainID) + + LEAF_ENTRY JIT_GetSharedNonGCStaticBaseNoCtor_InlineGetAppDomain + ; Check if r0 (moduleDomainID) is not a moduleID + tst r0, #1 + beq HaveLocalModule2 + + PATCHABLE_INLINE_GETAPPDOMAIN r2, JIT_GetSharedNonGCStaticBaseNoCtor__PatchTLSLabel + + ; Get the LocalModule, r0 will always be odd, so: r0 * 2 - 2 <=> (r0 >> 1) * 4 + ldr r2, [r2 , #AppDomain__m_sDomainLocalBlock + DomainLocalBlock__m_pModuleSlots] + add r2, r2, r0, LSL #1 + ldr r0, [r2, #-2] + + +HaveLocalModule2 + bx lr + LEAF_END + + +; ------------------------------------------------------------------ +; void* JIT_GetSharedGCStaticBase(SIZE_T moduleDomainID, DWORD dwClassDomainID) + + LEAF_ENTRY JIT_GetSharedGCStaticBase_InlineGetAppDomain + ; Check if r0 (moduleDomainID) is not a moduleID + tst r0, #1 + beq HaveLocalModule3 + + PATCHABLE_INLINE_GETAPPDOMAIN r2, JIT_GetSharedGCStaticBase__PatchTLSLabel + + ; Get the LocalModule, r0 will always be odd, so: r0 * 2 - 2 <=> (r0 >> 1) * 4 + ldr r2, [r2 , #AppDomain__m_sDomainLocalBlock + DomainLocalBlock__m_pModuleSlots] + add r2, r2, r0, LSL #1 + ldr r0, [r2, #-2] + +HaveLocalModule3 + ; If class is not initialized, bail to C++ helper + add r2, r0, #DomainLocalModule__m_pDataBlob + ldrb r2, [r2, r1] + tst r2, #1 + beq CallHelper3 + + ldr r0, [r0, #DomainLocalModule__m_pGCStatics] + bx lr + +CallHelper3 + ; Tail call Jit_GetSharedGCStaticBase_Helper + b JIT_GetSharedGCStaticBase_Helper + LEAF_END + + +; ------------------------------------------------------------------ +; void* JIT_GetSharedGCStaticBaseNoCtor(SIZE_T moduleDomainID, DWORD dwClassDomainID) + + LEAF_ENTRY JIT_GetSharedGCStaticBaseNoCtor_InlineGetAppDomain + ; Check if r0 (moduleDomainID) is not a moduleID + tst r0, #1 + beq HaveLocalModule4 + + PATCHABLE_INLINE_GETAPPDOMAIN r2, JIT_GetSharedGCStaticBaseNoCtor__PatchTLSLabel + + ; Get the LocalModule, r0 will always be odd, so: r0 * 2 - 2 <=> (r0 >> 1) * 4 + ldr r2, [r2 , #AppDomain__m_sDomainLocalBlock + DomainLocalBlock__m_pModuleSlots] + add r2, r2, r0, LSL #1 + ldr r0, [r2, #-2] + +HaveLocalModule4 + ldr r0, [r0, #DomainLocalModule__m_pGCStatics] + bx lr + LEAF_END + +; ------------------------------------------------------------------ +; End of the writeable code region + LEAF_ENTRY JIT_PatchedCodeLast + bx lr + LEAF_END + + +; Must be at very end of file + END diff --git a/src/vm/arm/pinvokestubs.S b/src/vm/arm/pinvokestubs.S new file mode 100644 index 0000000000..202792550e --- /dev/null +++ b/src/vm/arm/pinvokestubs.S @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +//; ==++== +//; + +//; +//; ==--== +#include "asmconstants.h" +#include "unixasmmacros.inc" + +.syntax unified +.thumb + +// ------------------------------------------------------------------ +// Macro to generate PInvoke Stubs. +// Params :- +// \__PInvokeStubFuncName : function which calls the actual stub obtained from VASigCookie +// \__PInvokeGenStubFuncName : function which generates the IL stubs for PInvoke +// \__PInvokeStubWorkerName : prefix of the function name for the stub +// \VASigCookieReg : register which contains the VASigCookie +// \SaveFPArgs : "1" or "0" . For varidic functions FP Args are not present in FP regs +// So need not save FP Args registers for vararg Pinvoke +.macro PINVOKE_STUB __PInvokeStubFuncName,__PInvokeGenStubFuncName,__PInvokeStubWorkerName,VASigCookieReg,SaveFPArgs + + NESTED_ENTRY \__PInvokeStubFuncName, _TEXT, NoHandler + + // save reg value before using the reg + PROLOG_PUSH {\VASigCookieReg} + + // get the stub + ldr \VASigCookieReg, [\VASigCookieReg,#VASigCookie__pNDirectILStub] + + // if null goto stub generation + cbz \VASigCookieReg, \__PInvokeStubFuncName\()Label + + EPILOG_STACK_FREE 4 + EPILOG_BRANCH_REG \VASigCookieReg + +\__PInvokeStubFuncName\()Label: + EPILOG_POP {\VASigCookieReg} + EPILOG_BRANCH \__PInvokeGenStubFuncName + + NESTED_END \__PInvokeStubFuncName, _TEXT + + + NESTED_ENTRY \__PInvokeGenStubFuncName, _TEXT, NoHandler + + PROLOG_WITH_TRANSITION_BLOCK 0, \SaveFPArgs + + // r2 = UnmanagedTarget\ MethodDesc + mov r2, r12 + + // r1 = VaSigCookie + .ifnc \VASigCookieReg, r1 + mov r1, \VASigCookieReg + .endif + + // r0 = pTransitionBlock + add r0, sp, #__PWTB_TransitionBlock + + // save hidden arg + mov r4, r12 + + bl \__PInvokeStubWorkerName + + // restore hidden arg (method desc or unmanaged target) + mov r12, r4 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + EPILOG_BRANCH \__PInvokeStubFuncName + + NESTED_END \__PInvokeGenStubFuncName, _TEXT + +.endmacro + +// ------------------------------------------------------------------ +// VarargPInvokeStub & VarargPInvokeGenILStub +// There is a separate stub when the method has a hidden return buffer arg. +// +// in: +// r0 = VASigCookie* +// r12 = MethodDesc * +// +PINVOKE_STUB VarargPInvokeStub, VarargPInvokeGenILStub, VarargPInvokeStubWorker, r0, 0 + +// ------------------------------------------------------------------ +// GenericPInvokeCalliHelper & GenericPInvokeCalliGenILStub +// Helper for generic pinvoke calli instruction +// +// in: +// r4 = VASigCookie* +// r12 = Unmanaged target +// +PINVOKE_STUB GenericPInvokeCalliHelper, GenericPInvokeCalliGenILStub, GenericPInvokeCalliStubWorker r4, 1 + +// ------------------------------------------------------------------ +// VarargPInvokeStub_RetBuffArg & VarargPInvokeGenILStub_RetBuffArg +// Vararg PInvoke Stub when the method has a hidden return buffer arg +// +// in: +// r1 = VASigCookie* +// r12 = MethodDesc* +// +PINVOKE_STUB VarargPInvokeStub_RetBuffArg, VarargPInvokeGenILStub_RetBuffArg, VarargPInvokeStubWorker, r1, 0 diff --git a/src/vm/arm/profiler.cpp b/src/vm/arm/profiler.cpp new file mode 100644 index 0000000000..d7ddd5ca7c --- /dev/null +++ b/src/vm/arm/profiler.cpp @@ -0,0 +1,358 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +// +// FILE: profiler.cpp +// + +// + +// +// ====================================================================================== + +#include "common.h" + +#ifdef PROFILING_SUPPORTED +#include "proftoeeinterfaceimpl.h" + +MethodDesc *FunctionIdToMethodDesc(FunctionID functionID); + +// TODO: move these to some common.h file +// FLAGS +#define PROFILE_ENTER 0x1 +#define PROFILE_LEAVE 0x2 +#define PROFILE_TAILCALL 0x4 + +typedef struct _PROFILE_PLATFORM_SPECIFIC_DATA +{ + UINT32 r0; // Keep r0 & r1 contiguous to make returning 64-bit results easier + UINT32 r1; + void *R11; + void *Pc; + union // Float arg registers as 32-bit (s0-s15) and 64-bit (d0-d7) + { + UINT32 s[16]; + UINT64 d[8]; + }; + FunctionID functionId; + void *probeSp; // stack pointer of managed function + void *profiledSp; // location of arguments on stack + LPVOID hiddenArg; + UINT32 flags; +} PROFILE_PLATFORM_SPECIFIC_DATA, *PPROFILE_PLATFORM_SPECIFIC_DATA; + + +/* + * ProfileGetIPFromPlatformSpecificHandle + * + * This routine takes the platformSpecificHandle and retrieves from it the + * IP value. + * + * Parameters: + * handle - the platformSpecificHandle passed to ProfileEnter/Leave/Tailcall + * + * Returns: + * The IP value stored in the handle. + */ +UINT_PTR ProfileGetIPFromPlatformSpecificHandle(void *handle) +{ + LIMITED_METHOD_CONTRACT; + + PROFILE_PLATFORM_SPECIFIC_DATA* pData = (PROFILE_PLATFORM_SPECIFIC_DATA*)handle; + return (UINT_PTR)pData->Pc; +} + + +/* + * ProfileSetFunctionIDInPlatformSpecificHandle + * + * This routine takes the platformSpecificHandle and functionID, and assign + * functionID to functionID field of platformSpecificHandle. + * + * Parameters: + * pPlatformSpecificHandle - the platformSpecificHandle passed to ProfileEnter/Leave/Tailcall + * functionID - the FunctionID to be assigned + * + * Returns: + * None + */ +void ProfileSetFunctionIDInPlatformSpecificHandle(void * pPlatformSpecificHandle, FunctionID functionID) +{ + LIMITED_METHOD_CONTRACT; + _ASSERTE(pPlatformSpecificHandle != NULL); + _ASSERTE(functionID != NULL); + + PROFILE_PLATFORM_SPECIFIC_DATA * pData = reinterpret_cast(pPlatformSpecificHandle); + pData->functionId = functionID; +} + +/* + * ProfileArgIterator::ProfileArgIterator + * + * Constructor. Does almost nothing. Init must be called after construction. + * + */ +ProfileArgIterator::ProfileArgIterator(MetaSig * pSig, void * platformSpecificHandle) + : m_argIterator(pSig) +{ + WRAPPER_NO_CONTRACT; + + _ASSERTE(pSig != NULL); + _ASSERTE(platformSpecificHandle != NULL); + + m_handle = platformSpecificHandle; + PROFILE_PLATFORM_SPECIFIC_DATA* pData = (PROFILE_PLATFORM_SPECIFIC_DATA*)m_handle; + + // unwind a frame and get the SP for the profiled method to make sure it matches + // what the JIT gave us +#ifdef _DEBUG + { +/* + Foo() { + Bar(); + } + +Stack for the above call will look as follows (stack growing downwards): + + | + | Stack Args for Foo | + | pre spill r0-r3 | + | LR | + | R11 | + | Locals of Foo | + | Stack Args for Bar | + | pre spill r0-r3 | __________this Sp value is saved in profiledSP + | LR | + | R11 | + | Satck saved in prolog of Bar | _______ call to profiler hook is made here_____this Sp value is saved in probeSP + | | + + +*/ + + // setup the context to represent the frame that called ProfileEnterNaked + CONTEXT ctx; + memset(&ctx, 0, sizeof(CONTEXT)); + ctx.Sp = (UINT)pData->probeSp; + ctx.R11 = (UINT)pData->R11; + ctx.Pc = (UINT)pData->Pc; + // For some functions which do localloc, sp is saved in r9. In order to perform unwinding for functions r9 must be set in the context. + // r9 is stored at offset (sizeof(PROFILE_PLATFORM_SPECIFIC_DATA) (this also includes the padding done for 8-byte stack alignement) + size required for (r0,r3)) bytes from pData + ctx.R9 = *((UINT*)pData + (sizeof(PROFILE_PLATFORM_SPECIFIC_DATA) + 8)/4); + + // walk up a frame to the caller frame (called the managed method which + // called ProfileEnterNaked) + Thread::VirtualUnwindCallFrame(&ctx); + + // add the prespill register(r0-r3) size to get the stack pointer of previous function + _ASSERTE(pData->profiledSp == (void*)(ctx.Sp - 4*4)); + } +#endif // _DEBUG + + // Get the hidden arg if there is one + MethodDesc * pMD = FunctionIdToMethodDesc(pData->functionId); + + if ( (pData->hiddenArg == NULL) && + (pMD->RequiresInstArg() || pMD->AcquiresInstMethodTableFromThis()) ) + { + // In the enter probe, the JIT may not have pushed the generics token onto the stack yet. + // Luckily, we can inspect the registers reliably at this point. + if (pData->flags & PROFILE_ENTER) + { + _ASSERTE(!((pData->flags & PROFILE_LEAVE) || (pData->flags & PROFILE_TAILCALL))); + + if (pMD->AcquiresInstMethodTableFromThis()) + { + pData->hiddenArg = GetThis(); + } + else + { + // The param type arg comes after the return buffer argument and the "this" pointer. + int index = 0; + + if (m_argIterator.HasThis()) + { + index++; + } + + if (m_argIterator.HasRetBuffArg()) + { + index++; + } + + pData->hiddenArg = *(LPVOID*)((LPBYTE)pData->profiledSp + (index * sizeof(SIZE_T))); + } + } + else + { + EECodeInfo codeInfo((PCODE)pData->Pc); + + // We want to pass the caller SP here. + pData->hiddenArg = EECodeManager::GetExactGenericsToken((SIZE_T)(pData->profiledSp), &codeInfo); + } + } +} + + +/* + * ProfileArgIterator::~ProfileArgIterator + * + * Destructor, releases all resources. + * + */ +ProfileArgIterator::~ProfileArgIterator() +{ + LIMITED_METHOD_CONTRACT; + + m_handle = NULL; +} + + +/* + * ProfileArgIterator::GetNextArgAddr + * + * After initialization, this method is called repeatedly until it + * returns NULL to get the address of each arg. Note: this address + * could be anywhere on the stack. + * + * Returns: + * Address of the argument, or NULL if iteration is complete. + */ +LPVOID ProfileArgIterator::GetNextArgAddr() +{ + WRAPPER_NO_CONTRACT; + + _ASSERTE(m_handle != NULL); + + PROFILE_PLATFORM_SPECIFIC_DATA* pData = (PROFILE_PLATFORM_SPECIFIC_DATA*)m_handle; + + if ((pData->flags & PROFILE_LEAVE) || (pData->flags & PROFILE_TAILCALL)) + { + _ASSERTE(!"GetNextArgAddr() - arguments are not available in leave and tailcall probes"); + return NULL; + } + + int argOffset = m_argIterator.GetNextOffset(); + + // argOffset of TransitionBlock::InvalidOffset indicates that we're done + if (argOffset == TransitionBlock::InvalidOffset) + { + return NULL; + } + + if (TransitionBlock::IsFloatArgumentRegisterOffset(argOffset)) + { + // Arguments which land up in floating point registers are contained entirely within those + // registers (they're never split onto the stack). + return ((BYTE *)&pData->d) + (argOffset - TransitionBlock::GetOffsetOfFloatArgumentRegisters()); + } + + // Argument lives in one or more general registers (and possibly overflows onto the stack). + return (LPBYTE)pData->profiledSp + (argOffset - TransitionBlock::GetOffsetOfArgumentRegisters()); +} + +/* + * ProfileArgIterator::GetHiddenArgValue + * + * Called after initialization, any number of times, to retrieve any + * hidden argument, so that resolution for Generics can be done. + * + * Parameters: + * None. + * + * Returns: + * Value of the hidden parameter, or NULL if none exists. + */ +LPVOID ProfileArgIterator::GetHiddenArgValue(void) +{ + LIMITED_METHOD_CONTRACT; + + PROFILE_PLATFORM_SPECIFIC_DATA* pData = (PROFILE_PLATFORM_SPECIFIC_DATA*)m_handle; + + return pData->hiddenArg; +} + +/* + * ProfileArgIterator::GetThis + * + * Called after initialization, any number of times, to retrieve any + * 'this' pointer. + * + * Parameters: + * None. + * + * Returns: + * Address of the 'this', or NULL if none exists. + */ +LPVOID ProfileArgIterator::GetThis(void) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + PROFILE_PLATFORM_SPECIFIC_DATA* pData = (PROFILE_PLATFORM_SPECIFIC_DATA*)m_handle; + MethodDesc * pMD = FunctionIdToMethodDesc(pData->functionId); + + // We guarantee to return the correct "this" pointer in the enter probe. + // For the leave and tailcall probes, we only return a valid "this" pointer if it is the generics token. + if (pData->hiddenArg != NULL) + { + if (pMD->AcquiresInstMethodTableFromThis()) + { + return pData->hiddenArg; + } + } + + if (pData->flags & PROFILE_ENTER) + { + if (m_argIterator.HasThis()) + { + return *(LPVOID*)((LPBYTE)pData->profiledSp); + } + } + + return NULL; +} + +/* + * ProfileArgIterator::GetReturnBufferAddr + * + * Called after initialization, any number of times, to retrieve the + * address of the return buffer. NULL indicates no return value. + * + * Parameters: + * None. + * + * Returns: + * Address of the return buffer, or NULL if none exists. + */ +LPVOID ProfileArgIterator::GetReturnBufferAddr(void) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + PROFILE_PLATFORM_SPECIFIC_DATA* pData = (PROFILE_PLATFORM_SPECIFIC_DATA*)m_handle; + MethodDesc * pMD = FunctionIdToMethodDesc(pData->functionId); + + if (m_argIterator.HasRetBuffArg()) + { + return (LPVOID)pData->r0; + } + + if (m_argIterator.GetFPReturnSize() != 0) + return &pData->d[0]; + + if (m_argIterator.GetSig()->GetReturnType() != ELEMENT_TYPE_VOID) + return &pData->r0; + else + return NULL; +} + +#endif // PROFILING_SUPPORTED diff --git a/src/vm/arm/stubs.cpp b/src/vm/arm/stubs.cpp new file mode 100644 index 0000000000..0b069da47e --- /dev/null +++ b/src/vm/arm/stubs.cpp @@ -0,0 +1,3948 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +// +// File: stubs.cpp +// +// This file contains stub functions for unimplemented features need to +// run on the ARM platform. + +#include "common.h" +#include "jitinterface.h" +#include "comdelegate.h" +#include "invokeutil.h" +#include "excep.h" +#include "class.h" +#include "field.h" +#include "dllimportcallback.h" +#include "dllimport.h" +#ifdef FEATURE_REMOTING +#include "remoting.h" +#endif +#include "eeconfig.h" +#include "cgensys.h" +#include "asmconstants.h" +#include "security.h" +#include "securitydescriptor.h" +#include "virtualcallstub.h" +#include "gcdump.h" +#include "rtlfunctions.h" +#include "codeman.h" +#include "tls.h" +#include "ecall.h" +#include "threadsuspend.h" + +// target write barriers +EXTERN_C void JIT_WriteBarrier(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_End(); +EXTERN_C void JIT_CheckedWriteBarrier(Object **dst, Object *ref); +EXTERN_C void JIT_CheckedWriteBarrier_End(); +EXTERN_C void JIT_ByRefWriteBarrier_End(); +EXTERN_C void JIT_ByRefWriteBarrier_SP(Object **dst, Object *ref); + +// source write barriers +EXTERN_C void JIT_WriteBarrier_SP_Pre(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_SP_Pre_End(); +EXTERN_C void JIT_WriteBarrier_SP_Post(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_SP_Post_End(); +EXTERN_C void JIT_WriteBarrier_MP_Pre(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_MP_Pre_End(); +EXTERN_C void JIT_WriteBarrier_MP_Post(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_MP_Post_End(); + +EXTERN_C void JIT_CheckedWriteBarrier_SP_Pre(Object **dst, Object *ref); +EXTERN_C void JIT_CheckedWriteBarrier_SP_Pre_End(); +EXTERN_C void JIT_CheckedWriteBarrier_SP_Post(Object **dst, Object *ref); +EXTERN_C void JIT_CheckedWriteBarrier_SP_Post_End(); +EXTERN_C void JIT_CheckedWriteBarrier_MP_Pre(Object **dst, Object *ref); +EXTERN_C void JIT_CheckedWriteBarrier_MP_Pre_End(); +EXTERN_C void JIT_CheckedWriteBarrier_MP_Post(Object **dst, Object *ref); +EXTERN_C void JIT_CheckedWriteBarrier_MP_Post_End(); + +EXTERN_C void JIT_ByRefWriteBarrier_SP_Pre(); +EXTERN_C void JIT_ByRefWriteBarrier_SP_Pre_End(); +EXTERN_C void JIT_ByRefWriteBarrier_SP_Post(); +EXTERN_C void JIT_ByRefWriteBarrier_SP_Post_End(); +EXTERN_C void JIT_ByRefWriteBarrier_MP_Pre(); +EXTERN_C void JIT_ByRefWriteBarrier_MP_Pre_End(); +EXTERN_C void JIT_ByRefWriteBarrier_MP_Post(Object **dst, Object *ref); +EXTERN_C void JIT_ByRefWriteBarrier_MP_Post_End(); + +EXTERN_C void JIT_PatchedWriteBarrierStart(); +EXTERN_C void JIT_PatchedWriteBarrierLast(); + +#ifndef DACCESS_COMPILE +//----------------------------------------------------------------------- +// InstructionFormat for conditional jump. +//----------------------------------------------------------------------- +class ThumbCondJump : public InstructionFormat +{ + public: + ThumbCondJump() : InstructionFormat(InstructionFormat::k16) + { + LIMITED_METHOD_CONTRACT; + } + + virtual UINT GetSizeOfInstruction(UINT refsize, UINT variationCode) + { + LIMITED_METHOD_CONTRACT + + _ASSERTE(refsize == InstructionFormat::k16); + + return 2; + } + + virtual UINT GetHotSpotOffset(UINT refsize, UINT variationCode) + { + LIMITED_METHOD_CONTRACT + + _ASSERTE(refsize == InstructionFormat::k16); + + return 4; + } + + //CB{N}Z Rn,