mirror of
https://github.com/VSadov/Satori.git
synced 2025-06-08 03:27:04 +09:00
Fix trailing whitespaces (#40891)
* Trim trailing whitespaces * Match raw with rendered * Delete extra asterisks and | * Update ELT Hooks - tail calls.md Co-authored-by: Jan Kotas <jkotas@microsoft.com>
This commit is contained in:
parent
d9f0ba704d
commit
d14b50ae21
141 changed files with 1128 additions and 1219 deletions
|
@ -58,7 +58,7 @@ internal static partial class Interop
|
|||
```
|
||||
|
||||
As shown above, platforms may be additive, in that an assembly may use functionality from multiple folders, e.g. System.IO.FileSystem's Linux build will use functionality both from Unix (common across all Unix systems) and from Linux (specific to Linux and not available across non-Linux Unix systems).
|
||||
|
||||
|
||||
- Interop.*.cs files are created in a way such that every assembly consuming the file will need every DllImport it contains.
|
||||
- If multiple related DllImports will all be needed by every consumer, they may be declared in the same file, named for the functionality grouping, e.g. Interop.IOErrors.cs.
|
||||
- Otherwise, in the limit (and the expected case for most situations) each Interop.*.cs file will contain a single DllImport and associated interop types (e.g. the structs used with that signature) and helper wrappers, e.g. Interop.strerror.cs.
|
||||
|
@ -104,7 +104,7 @@ internal static partial class Interop // contents of Common\src\Interop\Windows\
|
|||
|
||||
```
|
||||
(Note that this will likely result in some extra constants defined in each assembly that uses interop, which minimally violates one of the goals, but it's very minimal.)
|
||||
|
||||
|
||||
- .csproj project files then include the interop code they need, e.g.
|
||||
```XML
|
||||
<ItemGroup Condition=" '$(TargetsUnix)' == 'true' ">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Method Descriptor
|
||||
Method Descriptor
|
||||
=================
|
||||
|
||||
Author: Jan Kotas ([@jkotas](https://github.com/jkotas)) - 2006
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Type System Overview
|
||||
Type System Overview
|
||||
====================
|
||||
|
||||
Author: David Wrighton ([@davidwrighton](https://github.com/davidwrighton)) - 2010
|
||||
|
|
|
@ -17,7 +17,6 @@ post/pre increment, perhaps like this: `Foo(j, a[j++])`. Here `j` is updated vi
|
|||
when the second arg is evaluated, so the earlier uses of `j` would need to be evaluated and
|
||||
saved in a new LclVar.
|
||||
|
||||
|
||||
One simple approach would be to create new single definition, single use LclVars for every argument
|
||||
that is passed. This would preserve the evaluation order. However, it would potentially create
|
||||
hundreds of LclVar for moderately sized methods and that would overflow the limited number of
|
||||
|
@ -25,7 +24,6 @@ tracked local variables in the JIT. One observation is that many arguments to m
|
|||
either constants or LclVars and can be set up anytime we want. They usually will not need a
|
||||
new LclVar to preserve the order of evaluation rule.
|
||||
|
||||
|
||||
Each argument is an arbitrary expression tree. The JIT tracks a summary of observable side-effects
|
||||
using a set of five bit flags in every GenTree node: `GTF_ASG`, `GTF_CALL`, `GTF_EXCEPT`, `GTF_GLOB_REF`,
|
||||
and `GTF_ORDER_SIDEEFF`. These flags are propagated up the tree so that the top node has a particular
|
||||
|
|
|
@ -309,7 +309,7 @@ After LSRA, the graph has the following properties:
|
|||
|
||||
- However, if such a node is constrained to a set of registers,
|
||||
and its current location does not satisfy that requirement, LSRA
|
||||
must insert a `GT_COPY` node between the node and its parent.
|
||||
must insert a `GT_COPY` node between the node and its parent.
|
||||
The `_gtRegNum` on the `GT_COPY` node must satisfy the register
|
||||
requirement of the parent.
|
||||
|
||||
|
|
|
@ -606,7 +606,7 @@ public static int PopCount(ulong bitVectorArg)
|
|||
|
||||
#### Notes
|
||||
The sample I'm going to walk through implements support for pop count (counting the number of '1' bits in a 64-bit value).
|
||||
|
||||
|
||||
We're going to start by assuming that we have a method with a known signature that implements PopCount.
|
||||
Here's the implementation we're going to use. It simply takes the input value, and keeps anding with one, and then shifting right.
|
||||
We're first going to simply recognize the name and signature, and replace the method call with a simple PopCnt IR node.
|
||||
|
|
|
@ -7,8 +7,6 @@ Profiler attach is a feature that allows you to attach a profiler to an already
|
|||
|
||||
Please note! You can't just take any profiler you bought and suddenly be able to attach it to a running application. The profiler must be built with "attachability" in mind. So if you're a profiler developer looking to pump some attachability into your product, read on--this article is for you. Everyone else, this article will probably be less useful--but just as riveting.
|
||||
|
||||
#
|
||||
|
||||
# The Players
|
||||
|
||||
So how do you get your profiler attached to a running process? The process has already started, and the CLR code which interrogates the environment to determine whether to load a profiler has already run. So how do you kick the process into loading your profiler? The answer: Another process!
|
||||
|
@ -57,8 +55,6 @@ From your InitializeForAttach implementation, your profiler will call SetEventMa
|
|||
|
||||
It was impossible to enable all profiling scenarios for attach in the time we had for the V4 release. So only profilers that do **sampling** and **memory** analysis will function properly after attaching to a live process. Attempts to use other profiling APIs after attach will be met with CORPROF\_E\_UNSUPPORTED\_FOR\_ATTACHING\_PROFILER.
|
||||
|
||||
###
|
||||
|
||||
## Specific Callback Limitations
|
||||
|
||||
When your attaching profiler calls SetEventMask, you will be limited to only those event mask flags present in the COR\_PRF\_ALLOWABLE\_AFTER\_ATTACH bitmask (you'll find it in corprof.idl). Any other flags, and SetEventMask will return CORPROF\_E\_UNSUPPORTED\_FOR\_ATTACHING\_PROFILER.
|
||||
|
@ -96,15 +92,13 @@ So here's the catch. What if a V4 app starts up in background GC mode _without_
|
|||
|
||||
Of course, you could forcibly turn off concurrent / background mode every time the app starts up via a config file:
|
||||
|
||||
|
|
||||
|
||||
\<configuration\>
|
||||
\<runtime\>
|
||||
\<gcConcurrent enabled="false"/\>
|
||||
\</runtime\>
|
||||
\</configuration\>
|
||||
|
||||
|
|
||||
```xml
|
||||
<configuration>
|
||||
<runtime>
|
||||
<gcConcurrent enabled="false"/>
|
||||
</runtime>
|
||||
</configuration>
|
||||
```
|
||||
|
||||
But you don't really want to be running your apps with a sub-optimal GC mode all the time, just on the off-chance you might need to attach a memory profiler to it. If you suspect you might need to do some memory profiling of a client app, you should just start up your app with the memory profiler to begin with.
|
||||
|
||||
|
|
|
@ -151,7 +151,5 @@ It may be beneficial to program your profiler such that, upon attaching to the p
|
|||
|
||||
It’s worth reiterating a limitation I stated in the first attach post (linked above): the ObjectAllocated() callback is unavailable to profilers that attach to running processes. Therefore, any logic your profiler has that assumes it gets all the ObjectAllocated() callbacks will need to be addressed. Any objects newly allocated since the last GC may still be unknown to your profiler until it comes across their references via GC callbacks during the next GC (unless your profiler comes across those objects in other ways—example: as parameters to methods you hook with the Enter/Leave/Tailcall probes).
|
||||
|
||||
|
||||
|
||||
OK, that about covers the first steps your profiler should take once it attaches to a running process. It will either need to use lazy catch-up or the catch-up enumerations (or, quite likely, a combination of both). When using the enumerations, be careful to avoid holes (by calling the enumeration methods from inside ProfilerAttachComplete()), and be resilient to receiving information duplicated across the enumeration and the load / unload events. For memory profilers, be wary of GCs already in progress at the time your profiler attaches, and consider inducing your own GC at attach-time to build your initial cache of GC objects.
|
||||
|
||||
|
|
|
@ -15,9 +15,8 @@ Environment variables --\> Registry --\> Profiler DLL on File system.
|
|||
|
||||
The first link in this chain is to check the environment variables inside the process that should be profiled. If you're running the process from a command-prompt, you can just try a "set co" from the command prompt:
|
||||
|
||||
|
|
||||
```
|
||||
**C:\>** set co
|
||||
C:\> set co
|
||||
(blah blah, other vars beginning with "co")
|
||||
```
|
||||
|
||||
|
@ -25,7 +24,6 @@ The first link in this chain is to check the environment variables inside the pr
|
|||
Cor_Enable_Profiling=0x1
|
||||
COR_PROFILER={C5F90153-B93E-4138-9DB7-EB7156B07C4C}
|
||||
```
|
||||
|
|
||||
|
||||
If your scenario doesn't allow you to just run the process from a command prompt, like say an asp.net scenario, you may want to attach a debugger to the process that's supposed to be profiled, or use IFEO (HKEY\_LOCAL\_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options) to force a debugger to start when the worker process starts. In the debugger, you can then use "!peb" to view the environment block, which will include the environment variables.
|
||||
|
||||
|
@ -62,4 +60,3 @@ or even set a breakpoint inside your Profiler DLL's **DllMain.** Now go, and s
|
|||
If you're still going strong, set a breakpoint in your profiler's **Initialize** () callback. Failures here are actually a popular cause for activation problems. Inside your Initialize() callback, your profiler is likely calling QueryInterface for the ICorProfilerInfoX interface of your choice, and then calling SetEventMask, and doing other initialization-related tasks, like calling SetEnterLeaveFunctionHooks(2). Do any of these fail? Is your Initialize() callback returning a failure HRESULT?
|
||||
|
||||
Hopefully by now you've isolated the failure point. If not, and your Initialize() is happily returning S\_OK, then your profiler is apparently loading just fine. At least it is when you're debugging it. :-)
|
||||
|
||||
|
|
|
@ -9,9 +9,8 @@ SOS.DLL is a debugger extension DLL that ships with the CLR. You'll find it sit
|
|||
|
||||
In windbg, you'll need mscorwks.dll to load first, and then you can load SOS. Often, I don't need SOS until well into my debugging session, at which point mscorwks.dll has already been loaded anyway. However, there are some cases where you'd like SOS loaded at the first possible moment, so you can use some of its commands early (like !bpmd to set a breakpoint on a managed method). So a surefire way to get SOS loaded ASAP is to have the debugger break when mscorwks gets loaded (e.g., "sxe ld mscorwks"). Once mscorwks is loaded, you can load SOS using the .loadby command:
|
||||
|
||||
|
|
||||
```
|
||||
0:000\> **sxe ld mscorwks**
|
||||
0:000\> sxe ld mscorwks
|
||||
0:000\> g
|
||||
ModLoad: 79e70000 7a3ff000 C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll
|
||||
eax=00000000 ebx=00000000 ecx=00000000 edx=00000000 esi=7efdd000 edi=20000000
|
||||
|
@ -19,9 +18,8 @@ In windbg, you'll need mscorwks.dll to load first, and then you can load SOS. O
|
|||
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
|
||||
ntdll!NtMapViewOfSection+0x12:
|
||||
77a1a9fa c22800 ret 28h
|
||||
0:000\> **.loadby sos mscorwks**
|
||||
0:000\> .loadby sos mscorwks
|
||||
```
|
||||
|
|
||||
|
||||
With SOS loaded, you can now use its commands to inspect the various IDs that the profiling API passes to your profiler.
|
||||
|
||||
|
@ -35,7 +33,6 @@ As far as your profiler is concerned, a FunctionID is just an opaque number. It
|
|||
|
||||
Ok, so FunctionID = (MethodDesc \*). How does that help you? SOS just so happens to have a command to inspect MethodDescs: !dumpmd. So if you're in a debugger looking at your profiler code that's operating on a FunctionID, it can beneficial to you to find out which function that FunctionID actually refers to. In the example below, the debugger will break in my proifler's JITCompilationStarted callback and look at the FunctionID. It's assumed that you've already loaded SOS as per above.
|
||||
|
||||
|
|
||||
```
|
||||
0:000\> bu UnitTestSampleProfiler!SampleCallbackImpl::JITCompilationStarted
|
||||
0:000\> g
|
||||
|
@ -50,36 +47,30 @@ Breakpoint 0 hit
|
|||
UnitTestSampleProfiler!SampleCallbackImpl::JITCompilationStarted:
|
||||
10003fc0 55 push ebp
|
||||
```
|
||||
|
|
||||
|
||||
The debugger is now sitting at the beginning of my profiler's JITCompilationStarted callback. Let's take a look at the parameters.
|
||||
|
||||
|
|
||||
```
|
||||
0:000\> dv
|
||||
this = 0x00c133f8
|
||||
**functionID = 0x1e3170**
|
||||
functionID = 0x1e3170
|
||||
fIsSafeToBlock = 1
|
||||
```
|
||||
|
|
||||
|
||||
Aha, that's the FunctionID about to get JITted. Now use SOS to see what that function really is.
|
||||
|
||||
|
|
||||
```
|
||||
0:000\> !dumpmd 0x1e3170
|
||||
Method Name: test.Class1.Main(System.String[])
|
||||
Class: 001e1288
|
||||
**MethodTable: 001e3180** mdToken: 06000001
|
||||
MethodTable: 001e3180 mdToken: 06000001
|
||||
Module: 001e2d8c
|
||||
IsJitted: no
|
||||
m\_CodeOrIL: ffffffff
|
||||
```
|
||||
|
|
||||
|
||||
Lots of juicy info here, though the Method Name typically is what helps me the most in my debugging sessions. mdToken tells us the metadata token for this method. MethodTable tells us where another internal CLR data structure is stored that contains information about the class containing the function. In fact, the profiing API's ClassID is simply a MethodTable \*. [Note: the "Class: 001e1288" in the output above is very different from the MethodTable, and thus different from the profiling API's ClassID. Don't let the name fool you!] So we could go and inspect a bit further by dumping information about the MethodTable:
|
||||
|
||||
|
|
||||
```
|
||||
0:000\> !dumpmt 0x001e3180
|
||||
EEClass: 001e1288
|
||||
|
@ -91,7 +82,6 @@ Lots of juicy info here, though the Method Name typically is what helps me the m
|
|||
Number of IFaces in IFaceMap: 0
|
||||
Slots in VTable: 6
|
||||
```
|
||||
|
|
||||
|
||||
And of course, !dumpmt can be used anytime you come across a ClassID and want more info on it.
|
||||
|
||||
|
@ -126,11 +116,9 @@ It would probably be quicker to list what _isn't_ useful! I encourage you to do
|
|||
|
||||
!bpmd lets you place a breakpoint on a managed method. Just specify the module name and the fully-qualified method name. For example:
|
||||
|
||||
|
|
||||
```
|
||||
!bpmd MyModule.exe MyNamespace.MyClass.Foo
|
||||
```
|
||||
|
|
||||
|
||||
If the method hasn't jitted yet, no worries. A "pending" breakpoint is placed. If your profiler performs IL rewriting, then using !bpmd on startup to set a managed breakpoint can be a handy way to break into the debugger just before your instrumented code will run (which, in turn, is typically just after your instrumented code has been jitted). This can help you in reproducing and diagnosing issues your profiler may run into when instrumenting particular functions (due to something interesting about the signature, generics, etc.).
|
||||
|
||||
|
|
|
@ -5,9 +5,6 @@ In my initial [post](DoStackSnapshot - Exception Filters.md) about DoStackSnapsh
|
|||
|
||||
The quick answer is that **nonvolatile (i.e., preserved), integer registers** should be valid. You don't really need many registers to walk the stack anyway. Obviously, you want a good stack pointer and instruction pointer. And hey, a frame pointer is handy when you come across an EBP-based frame in x86 (RBP on x64). These are all included in the set, of course. Specifically by architecture, you can trust these fields in your context:
|
||||
|
||||
x86: Edi, Esi, Ebx, Ebp, Esp, Eip
|
||||
x64: Rdi, Rsi, Rbx, Rbp, Rsp, Rip, R12:R15
|
||||
ia64: IntS0:IntS3, RsBSP, StIFS, RsPFS, IntSp, StIIP, StIPSR
|
||||
|
||||
|
||||
|
||||
- x86: Edi, Esi, Ebx, Ebp, Esp, Eip
|
||||
- x64: Rdi, Rsi, Rbx, Rbp, Rsp, Rip, R12:R15
|
||||
- ia64: IntS0:IntS3, RsBSP, StIFS, RsPFS, IntSp, StIIP, StIPSR
|
||||
|
|
|
@ -35,9 +35,9 @@ The filters are the things that come after "When". We all know that, when an exc
|
|||
|
||||
The thing you need to realize about DoStackSnapshot's behavior (indeed, CLR in general) is that the execution of a When clause is really a separate function call. In the above example, imagine we take a stack snapshot while inside Positive(). Our managed-only stack trace, as reported by DoStackSnapshot, would then look like this (stack grows up):
|
||||
|
||||
Positive
|
||||
Main
|
||||
Thrower
|
||||
Positive\
|
||||
Main\
|
||||
Thrower\
|
||||
Main
|
||||
|
||||
It's that highlighted Main that seems odd at first. While the exception is thrown inside Thrower(), the CLR needs to execute the filter clauses to figure out which Catch wins. These filter executions are actually _function calls_. Since filter clauses don't have their own names, we just use the name of the function containing the filter clause for stack reporting purposes. Thus, the highlighted Main above is the execution of a filter clause located inside Main (in this case, "When Positive()"). When each filter clause completes, we "return" back to Thrower() to continue our search for the filter that returns True. Since this is how the call stack is built up, that's what DoStackSnapshot will report.
|
||||
|
|
|
@ -5,79 +5,84 @@ The CLR Profiling API allows you to hook managed functions so that your profiler
|
|||
|
||||
### Setting up the hooks
|
||||
|
||||
1. On initialization, your profiler must call SetEnterLeaveFunctionHooks(2) to specify which functions inside your profiler should be called whenever a managed function is entered, returns, or exits via tail call, respectively.
|
||||
_(Profiler calls this…)_
|
||||
1. On initialization, your profiler must call SetEnterLeaveFunctionHooks(2) to specify which functions inside your profiler should be called whenever a managed function is entered, returns, or exits via tail call, respectively.
|
||||
|
||||
_(Profiler calls this…)_
|
||||
|
||||
```
|
||||
HRESULT SetEnterLeaveFunctionHooks(
|
||||
[in] FunctionEnter \*pFuncEnter,
|
||||
[in] FunctionLeave \*pFuncLeave,
|
||||
[in] FunctionTailcall \*pFuncTailcall);
|
||||
[in] FunctionEnter *pFuncEnter,
|
||||
[in] FunctionLeave *pFuncLeave,
|
||||
[in] FunctionTailcall *pFuncTailcall);
|
||||
```
|
||||
|
||||
_(Profiler implements these…)_
|
||||
```
|
||||
typedef void FunctionEnter(FunctionID funcID);
|
||||
typedef void FunctionLeave(FunctionID funcID);
|
||||
typedef void FunctionTailcall(FunctionID funcID);
|
||||
```
|
||||
_(Profiler implements these…)_
|
||||
|
||||
**OR**
|
||||
```
|
||||
typedef void FunctionEnter(FunctionID funcID);
|
||||
typedef void FunctionLeave(FunctionID funcID);
|
||||
typedef void FunctionTailcall(FunctionID funcID);
|
||||
```
|
||||
|
||||
_(Profiler calls this…)_
|
||||
```
|
||||
HRESULT SetEnterLeaveFunctionHooks2(
|
||||
[in] FunctionEnter2 *pFuncEnter,
|
||||
[in] FunctionLeave2 *pFuncLeave,
|
||||
[in] FunctionTailcall2 *pFuncTailcall);
|
||||
```
|
||||
**OR**
|
||||
|
||||
_(Profiler calls this…)_
|
||||
|
||||
_(Profiler implements these…)_
|
||||
```
|
||||
typedef void FunctionEnter2(
|
||||
FunctionID funcId,
|
||||
UINT_PTR clientData,
|
||||
COR_PRF_FRAME_INFO func,
|
||||
COR_PRF_FUNCTION_ARGUMENT_INFO *argumentInfo);
|
||||
```
|
||||
HRESULT SetEnterLeaveFunctionHooks2(
|
||||
[in] FunctionEnter2 *pFuncEnter,
|
||||
[in] FunctionLeave2 *pFuncLeave,
|
||||
[in] FunctionTailcall2 *pFuncTailcall);
|
||||
```
|
||||
|
||||
typedef void FunctionLeave2(
|
||||
FunctionID funcId,
|
||||
UINT_PTR clientData,
|
||||
COR_PRF_FRAME_INFO func,
|
||||
COR_PRF_FUNCTION_ARGUMENT_RANGE *retvalRange);
|
||||
_(Profiler implements these…)_
|
||||
|
||||
typedef void FunctionTailcall2(
|
||||
FunctionID funcId,
|
||||
UINT_PTR clientData,
|
||||
COR_PRF_FRAME_INFO func);
|
||||
```
|
||||
```
|
||||
typedef void FunctionEnter2(
|
||||
FunctionID funcId,
|
||||
UINT_PTR clientData,
|
||||
COR_PRF_FRAME_INFO func,
|
||||
COR_PRF_FUNCTION_ARGUMENT_INFO *argumentInfo);
|
||||
|
||||
This step alone does not cause the enter/leave/tailcall (ELT) hooks to be called. But you must do this on startup to get things rolling.
|
||||
typedef void FunctionLeave2(
|
||||
FunctionID funcId,
|
||||
UINT_PTR clientData,
|
||||
COR_PRF_FRAME_INFO func,
|
||||
COR_PRF_FUNCTION_ARGUMENT_RANGE *retvalRange);
|
||||
|
||||
2. At any time during the run, your profiler calls SetEventMask specifying COR\_PRF\_MONITOR\_ENTERLEAVE in the bitmask. Your profiler may set or reset this flag at any time to cause ELT hooks to be called or ignored, respectively.
|
||||
typedef void FunctionTailcall2(
|
||||
FunctionID funcId,
|
||||
UINT_PTR clientData,
|
||||
COR_PRF_FRAME_INFO func);
|
||||
```
|
||||
|
||||
This step alone does not cause the enter/leave/tailcall (ELT) hooks to be called. But you must do this on startup to get things rolling.
|
||||
|
||||
2. At any time during the run, your profiler calls SetEventMask specifying COR\_PRF\_MONITOR\_ENTERLEAVE in the bitmask. Your profiler may set or reset this flag at any time to cause ELT hooks to be called or ignored, respectively.
|
||||
|
||||
### FunctionIDMapper
|
||||
|
||||
In addition to the above two steps, your profiler may specify more granularly which managed functions should have ELT hooks compiled into them:
|
||||
|
||||
1. At any time, your profiler may call ICorProfilerInfo2::SetFunctionIDMapper to specify a special hook to be called when a function is JITted.
|
||||
1. At any time, your profiler may call ICorProfilerInfo2::SetFunctionIDMapper to specify a special hook to be called when a function is JITted.
|
||||
|
||||
_(Profiler calls this…)_
|
||||
```
|
||||
HRESULT SetFunctionIDMapper([in] FunctionIDMapper \*pFunc);
|
||||
```
|
||||
_(Profiler calls this…)_
|
||||
|
||||
```
|
||||
HRESULT SetFunctionIDMapper([in] FunctionIDMapper \*pFunc);
|
||||
```
|
||||
|
||||
_(Profiler implements this…)_
|
||||
```
|
||||
typedef UINT_PTR __stdcall FunctionIDMapper(
|
||||
FunctionID funcId,
|
||||
BOOL *pbHookFunction);
|
||||
```
|
||||
_(Profiler implements this…)_
|
||||
|
||||
```
|
||||
typedef UINT_PTR __stdcall FunctionIDMapper(
|
||||
FunctionID funcId,
|
||||
BOOL *pbHookFunction);
|
||||
```
|
||||
|
||||
|
||||
2. When FunctionIDMapper is called:
|
||||
a. Your profiler sets the pbHookFunction [out] parameter appropriately to determine whether the function identified by funcId should have ELT hooks compiled into it.
|
||||
a. Your profiler sets the pbHookFunction \[out] parameter appropriately to determine whether the function identified by funcId should have ELT hooks compiled into it.
|
||||
b. Of course, the primary purpose of FunctionIDMapper is to allow your profiler to specify an alternate ID for that function. Your profiler does this by returning that ID from FunctionIDMapper . The CLR will pass this alternate ID to your ELT hooks (as funcID if you're using the 1.x ELT, and as clientData if you're using the 2.x ELT).
|
||||
|
||||
### Writing your ELT hooks
|
||||
|
@ -92,8 +97,6 @@ The solution is “NGEN /Profile”. For example, if you run this command agains
|
|||
|
||||
`ngen install MyAssembly.dll /Profile`
|
||||
|
||||
|
||||
|
||||
it will NGEN MyAssembly.dll with the “Profile” flavor (also called “profiler-enhanced”). This flavor causes extra hooks to be baked in to enable features like ELT hooks, loader callbacks, managed/unmanaged code transition callbacks, and the JITCachedFunctionSearchStarted/Finished callbacks.
|
||||
|
||||
The original NGENd versions of all your assemblies still stay around in your NGEN cache. NGEN /Profile simply causes a new set of NGENd assemblies to be generated as well, marked as the “profiler-enhanced” set of NGENd assemblies. At run-time, the CLR determines which flavor should be loaded. If a profiler is attached and enables certain features that only work with profiler-enhanced (not regular) NGENd assemblies (such as ELT via a call to SetEnterLeaveFunctionHooks(2), or any of several other features that are requested by setting particular event flags via SetEventMask), then the CLR will only load profiler-enhanced NGENd images--and if none exist then the CLR degrades to JIT in order to support the features requested by the profiler. In contrast, if the profiler does not specify such event flags, or there is no profiler to begin with, then the CLR loads the regular-flavored NGENd assemblies.
|
||||
|
@ -128,4 +131,3 @@ Why do you care? Well, it's always good to know what price you're paying. If you
|
|||
### Next time...
|
||||
|
||||
That about covers it for the ELT basics. Next installment of this riveting series will talk about that enigma known as tailcall.
|
||||
|
||||
|
|
|
@ -121,9 +121,11 @@ Method 2: On tailcall, "mark" the FunctionID at the top of your stack as needing
|
|||
|
||||
With this strategy, for the duration of the call to Three(), the shadow stack will look like this:
|
||||
|
||||
```
|
||||
Three
|
||||
Helper (marked for deferred pop)
|
||||
Main
|
||||
```
|
||||
|
||||
which some might consider more user-friendly. And as soon as Three() returns, your profiler will sneakily do a double-pop leaving just this:
|
||||
|
||||
|
@ -163,9 +165,11 @@ Method 2: Shadow stack fails
|
|||
|
||||
At stage (4), the shadow stack looks like this:
|
||||
|
||||
```
|
||||
Helper
|
||||
Thread.Sleep (marked for "deferred pop")
|
||||
Main
|
||||
```
|
||||
|
||||
If you think it might be complicated to explain tail calls to your users so they can understand the Method 1 form of shadow stack presentation, just try explaining why it makes sense to present to them that Thread.Sleep() is calling Helper()!
|
||||
|
||||
|
|
|
@ -57,8 +57,6 @@ HRESULT GetFunctionInfo2([in] FunctionID funcId,
|
|||
|
||||
typeArgs[]: This is the array of **type arguments** to MyClass\<int\>.Foo\<float\>. So this will be an array of only one element: the ClassID for float. (The int in MyClass\<int\> is a type argument to MyClass, not to Foo, and you would only see that when you call GetClassIDInfo2 with MyClass\<int\>.)
|
||||
|
||||
##
|
||||
|
||||
## GetClassIDInfo2
|
||||
|
||||
OK, someone in parentheses said something about calling GetClassIDInfo2, so let’s do that. Since we got the ClassID for MyClass\<int\> above, let’s pass it to GetClassIDInfo2 to see what we get:
|
||||
|
@ -102,8 +100,6 @@ With a valid COR\_PRF\_FRAME\_INFO, GetFunctionInfo2 will give you helpful, spec
|
|||
|
||||
It’s worth noting here that there is a bug in GetFunctionInfo2, in that the [out] pClassId you get for the class containing the function can be wrong with generic virtual functions. Take a look at [this forum post](http://social.msdn.microsoft.com/Forums/en-US/netfxtoolsdev/thread/ed6f972f-712a-48df-8cce-74f8951503fa/) for more information and a workaround.
|
||||
|
||||
##
|
||||
|
||||
## ClassIDs & FunctionIDs vs. Metadata Tokens
|
||||
|
||||
Although you can infer this from the above, let’s take a breather and review. When you have multiple generic instantiations of a generic type, that type is defined with one mdTypeDef (metadata token), but you’ll see multiple ClassIDs (one per instantiation). When you have multiple generic instantiations of a generic method, it’s defined with one mdMethodDef (metadata token), but you’ll see multiple FunctionIDs (one per instantiation).
|
||||
|
|
|
@ -31,10 +31,6 @@ Yes, that is a good example. You are an astute reader. Memory profilers that w
|
|||
|
||||
# Going from metadata token to run-time ID
|
||||
|
||||
#
|
||||
|
||||
#
|
||||
|
||||
As I mentioned above, the safest way to do this is to build up your own map and do reverse-lookups as necessary. If that scheme meets your needs, then by all means do that, and stop reading! But in the cases where this is insufficient, you may need to resort to using GetFunctionFromToken(AndTypeArgs) and GetClassFromToken(AndTypeArgs). There is no simple, foolproof way to use these APIs safely, but here is your guideline:
|
||||
|
||||
**Never call GetFunctionFromToken(AndTypeArgs) and GetClassFromToken(AndTypeArgs) unless you’re certain the relevant types have been loaded.** (“Relevant types” include the ClassID containing the FunctionID whose mdMethodDef you pass to GetFunctionFromToken(AndTypeArgs), and the ClassID whose mdTypeDef you pass to GetClassFromToken(AndTypeArgs).) If these types have not been loaded, _you may cause them to be loaded now_! This is bad because:
|
||||
|
|
|
@ -27,7 +27,7 @@ HRESULT DoStackSnapshot(
|
|||
```
|
||||
And here’s what the CLR calls on your profiler (you can also find this in corprof.idl). You’ll pass a pointer to your implementation of this function in the callback parameter above.
|
||||
```
|
||||
typedef HRESULT \_\_stdcall StackSnapshotCallback(
|
||||
typedef HRESULT __stdcall StackSnapshotCallback(
|
||||
FunctionID funcId,
|
||||
UINT_PTR ip,
|
||||
COR_PRF_FRAME_INFO frameInfo,
|
||||
|
@ -77,46 +77,16 @@ Before I continue from this exciting cliffhanger, a brief interlude. Everyone k
|
|||
|
||||
Now that we’re speaking the same language. Let’s look at a mixed-mode stack:
|
||||
|
||||
|
|
||||
|
||||
```
|
||||
Unmanaged
|
||||
|
||||
|
|
||||
|
|
||||
|
||||
D (Managed)
|
||||
|
||||
|
|
||||
|
|
||||
|
||||
Unmanaged
|
||||
|
||||
|
|
||||
|
|
||||
|
||||
C (Managed)
|
||||
|
||||
|
|
||||
|
|
||||
|
||||
B (Managed)
|
||||
|
||||
|
|
||||
|
|
||||
|
||||
Unmanaged
|
||||
|
||||
|
|
||||
|
|
||||
|
||||
A (Managed)
|
||||
|
||||
|
|
||||
|
|
||||
|
||||
Main (Managed)
|
||||
|
||||
|
|
||||
```
|
||||
|
||||
Stepping back a bit, it’s worthwhile to understand why DoStackSnapshot exists in the first place. It’s there to help you walk _managed_ frames on the stack. If you tried to walk managed frames yourself, you would get unreliable results, particularly on 32 bits, because of some wacky calling conventions used in managed code. The CLR understands these calling conventions, and DoStackSnapshot is therefore in a uniquely suitable position to help you decode them. However, DoStackSnapshot is not a complete solution if you want to be able to walk the entire stack, including unmanaged frames. Here’s where you have a choice:
|
||||
|
||||
|
@ -145,81 +115,67 @@ But before you get too deep, note that the issue of whether and how to seed a st
|
|||
|
||||
For the truly adventurous profiler that is doing an asynchronous, cross-thread, seeded stack walk while filling in the unmanaged holes, here’s what it would look like.
|
||||
|
||||
|
|
||||
Block of Unmanaged Frames
|
||||
|
||||
Block of
|
||||
Unmanaged
|
||||
Frames
|
||||
|
||||
|
|
||||
1. You suspend the target thread (target thread’s suspend count is now 1)
|
||||
2. You get the target thread’s current register context
|
||||
3. You determine if the register context points to unmanaged code (e.g., call ICorProfilerInfo2::GetFunctionFromIP(), and see if you get back a 0 FunctionID)
|
||||
4. In this case the register context does point to unmanaged code, so you perform an unmanaged stack walk until you find the top-most managed frame (D)
|
||||
|
|
||||
|
|
||||
|
||||
Function D
|
||||
(Managed)
|
||||
```
|
||||
Function D
|
||||
(Managed)
|
||||
```
|
||||
|
||||
|
|
||||
1. You call DoStackSnapshot with your seed context. CLR suspends target thread again: its suspend count is now 2. Our sandwich begins.
|
||||
|
||||
1. CLR calls your StackSnapshotCallback with FunctionID for D.
|
||||
|
|
||||
|
|
||||
|
||||
Block of
|
||||
Unmanaged
|
||||
Frames
|
||||
```
|
||||
Block of
|
||||
Unmanaged
|
||||
Frames
|
||||
```
|
||||
|
||||
|
|
||||
1. CLR calls your StackSnapshotCallback with FunctionID=0. You’ll need to walk this block yourself. You can stop when you hit the first managed frame, or you can cheat: delay your unmanaged walk until sometime after your next callback, as the next callback will tell you exactly where the next managed frame begins (and thus where your unmanaged walk should end).
|
||||
|
|
||||
|
|
||||
|
||||
Function C
|
||||
(Managed)
|
||||
|
||||
|
|
||||
```
|
||||
Function C
|
||||
(Managed)
|
||||
```
|
||||
1. CLR calls your StackSnapshotCallback with FunctionID for C.
|
||||
|
|
||||
|
|
||||
|
||||
Function B
|
||||
(Managed)
|
||||
```
|
||||
Function B
|
||||
(Managed)
|
||||
```
|
||||
|
||||
|
|
||||
1. CLR calls your StackSnapshotCallback with FunctionID for B.
|
||||
|
|
||||
|
|
||||
|
||||
Block of
|
||||
Unmanaged
|
||||
Frames
|
||||
```
|
||||
Block of
|
||||
Unmanaged
|
||||
Frames
|
||||
```
|
||||
|
||||
|
|
||||
1. CLR calls your StackSnapshotCallback with FunctionID=0. Again, you’ll need to walk this block yourself.
|
||||
|
|
||||
|
|
||||
|
||||
Function A
|
||||
(Managed)
|
||||
```
|
||||
Function A
|
||||
(Managed)
|
||||
```
|
||||
|
||||
|
|
||||
1. CLR calls your StackSnapshotCallback with FunctionID for A.
|
||||
|
|
||||
|
|
||||
|
||||
Main
|
||||
(Managed)
|
||||
```
|
||||
Main
|
||||
(Managed)
|
||||
```
|
||||
|
||||
|
|
||||
1. CLR calls your StackSnapshotCallback with FunctionID for Main.
|
||||
2. DoStackSnapshot “resumes” target thread (its suspend count is now 1) and returns. Our sandwich is complete.
|
||||
|
||||
1. You resume target thread (its suspend count is now 0, so it’s resumed for real).
|
||||
|
|
||||
|
||||
**Triumph over evil**
|
||||
|
||||
|
|
|
@ -22,12 +22,6 @@ Typically, your profiler will also create a new thread at this point, call it yo
|
|||
|
||||
## ModuleLoadFinished Time
|
||||
|
||||
###
|
||||
|
||||
###
|
||||
|
||||
###
|
||||
|
||||
### Metadata Changes
|
||||
|
||||
As each module loads, you will likely need to add metadata so that your future ReJITs will have the tokens they need. What you do here heavily depends on the kind of instrumentation you want to do. I’m assuming you’re doing instrumentation that adds some calls from the user code into brand new profiler helper methods you will add somewhere. If you plan to instrument mscorlib, you will likely want to add those profiler helper methods into mscorlib (remember, mscorlib is not allowed to contain an AssemblyRef that points to any other assembly!). Otherwise, perhaps you plan to ship a managed helper assembly that will sit on your user’s disk, and all your profiler helper methods will reside in this on-disk managed helper assembly.
|
||||
|
@ -56,10 +50,6 @@ Now imagine your user has turned some dial on your out-of-process GUI, to reques
|
|||
- This is optional, and only need be done if you truly want this ReJIT request to apply to all unshared copies of the function. You’re perfectly welcome to ReJIT only those unshared copies you want (and / or the shared copy).
|
||||
- Now you can re-read the “Re-Request Prior ReJITs” section above. :-)
|
||||
|
||||
##
|
||||
|
||||
###
|
||||
|
||||
### More on AppDomains
|
||||
|
||||
This whole shared / multiple unshared business can get confusing. So to bring it home, consider your user. If your user expresses instrumentation intent at the level of a class/method name, then you pretty much want to ReJIT every copy of that function (all unshared copies plus the shared copy). But if your user expresses instrumentation intent at the level of a class/method name _plus AppDomain_ (think one single AppPool inside ASP.NET), then you’d only want to ReJIT the copy of the function that resides in the single ModuleID associated with that AppDomain.
|
||||
|
|
|
@ -3,17 +3,18 @@
|
|||
|
||||
If your profiler plays with metadata, you've undoubtedly come across signature blobs. They’re used to encode type information for method definitions & references, local variables, and a whole lot more. They’re wonderfully compact, recursively versatile, and sometimes, well, challenging to parse. Fortunately, [Rico Mariani](https://docs.microsoft.com/en-us/archive/blogs/ricom/) was feeling generous one day, and churned out a simple parser that can read these types of signatures:
|
||||
|
||||
MethodDefSig
|
||||
MethodRefSig
|
||||
StandAloneMethodSig
|
||||
FieldSig
|
||||
PropertySig
|
||||
LocalVarSig
|
||||
- MethodDefSig
|
||||
- MethodRefSig
|
||||
- StandAloneMethodSig
|
||||
- FieldSig
|
||||
- PropertySig
|
||||
- LocalVarSig
|
||||
|
||||
Here are the files:
|
||||
[sigparse.cpp](samples/sigparse.cpp) (Rico's signature parser)
|
||||
[sigformat.cpp](samples/sigformat.cpp) (An example extension to the parser)
|
||||
[PlugInToYourProfiler.cpp](samples/PlugInToYourProfiler.cpp) (Example code to plug the extension into your profiler)
|
||||
|
||||
- [sigparse.cpp](samples/sigparse.cpp) (Rico's signature parser)
|
||||
- [sigformat.cpp](samples/sigformat.cpp) (An example extension to the parser)
|
||||
- [PlugInToYourProfiler.cpp](samples/PlugInToYourProfiler.cpp) (Example code to plug the extension into your profiler)
|
||||
|
||||
Open up **sigparse.cpp** in your favorite editor and take a look at the grammar at the top. The grammar comes from the ECMA CLI spec. Jonathan Keljo has a [link](http://blogs.msdn.com/jkeljo/archive/2005/08/04/447726.aspx) to it from his blog. This tells you the types of signature blobs the parser can handle.
|
||||
|
||||
|
@ -26,6 +27,7 @@ Sigparse.cpp is structured without any dependencies on any headers, so you can e
|
|||
|
||||
Simply derive a new class from SigParser, and override the virtual functions. The functions you override are events to be handled as the parser traverses the signature in top-down fashion. For example, when the parser encounters a MethodDef, you might see calls to your overrides of:
|
||||
|
||||
```
|
||||
NotifyBeginMethod()
|
||||
NotifyParamCount()
|
||||
NotifyBeginRetType()
|
||||
|
@ -40,6 +42,7 @@ NotifyBeginMethod()
|
|||
NotifyEndParam()
|
||||
_… (more parameter notifications occur here if more parameters exist)_
|
||||
NotifyEndMethod()
|
||||
```
|
||||
|
||||
And yes, generics are handled as well.
|
||||
|
||||
|
@ -60,4 +63,3 @@ Don't worry, it's optional. I mentioned above that only signatures whose grammar
|
|||
The only gotcha is that TypeSpecs & MethodSpecs don’t have a unique byte that introduces them. For example, GENERICINST could indicate the beginning of a TypeSpec or a MethodSpec. You’ll see that SigParser::Parse() switches on the intro byte to determine what it’s looking at. So to keep things simple, you’ll want to add a couple more top-level functions to SigParser to parse TypeSpecs & MethodSpecs (say, ParseTypeSpec() & ParseMethodSpec()). You’d then call those functions instead of Parse() when you have a TypeSpec or MethodSpec on your hands. Of course, if you don’t care about TypeSpecs and MethodSpecs, you can use the code as is and not worry. But this stuff is so much fun, you’ll probably want to add the capability anyway.
|
||||
|
||||
Hope you find this useful. And thanks again to Rico Mariani for sigparse.cpp!
|
||||
|
||||
|
|
|
@ -9,14 +9,12 @@ Type forwarding is nothing new. However, in CLR V4, we are enabling type forwar
|
|||
|
||||
The example I’ll use where the .NET Framework uses type forwarding is the TimeZoneInfo class. In CLR V4, TimeZoneInfo is now forwarded from System.Core.dll to mscorlib.dll. If you open the CLR V4 copy of System.Core.dll in ildasm and choose Dump, you'll see the following:
|
||||
|
||||
|
|
||||
```
|
||||
.class extern /*27000004*/ forwarder System.TimeZoneInfo
|
||||
{
|
||||
.assembly extern mscorlib /*23000001*/
|
||||
}
|
||||
```
|
||||
|
|
||||
|
||||
In each assembly’s metadata is an exported types table. The above means that System.Core.dll's exported types table includes an entry for System.TimeZoneInfo (indexed by token 27000004). What's significant is that System.Core.dll no longer has a typeDef for System.TimeZoneInfo, only an exported type. The fact that the token begins at the left with 0x27 tells you that it's an mdtExportedType (not a mdtTypeDef, which begins at the left with 0x02).
|
||||
|
||||
|
@ -70,11 +68,11 @@ Note that, if you were to build the above C# code using the .NET 4.0 C# compiler
|
|||
Ok, so how do we run this pre-.NET 4.0 executable against .NET 4.0? A config file, of course. Paste the following into a file named Class1.exe.config that sits next to Class1.exe:
|
||||
|
||||
```
|
||||
<configuration\>
|
||||
<startup\>
|
||||
<supportedRuntime version="v4.0.20506"/>
|
||||
</startup\>
|
||||
</configuration\>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0.20506"/>
|
||||
</startup>
|
||||
</configuration>
|
||||
```
|
||||
|
||||
The above will force Class1.exe to bind against .NET 4.0 Beta 1. And when it comes time to look for TimeZoneInfo, the CLR will first look in System.Core.dll, find the exported types table entry, and then hop over to mscorlib.dll to load the type. What does that look like to your profiler? Make your guess and hold that thought. First, another walkthrough…
|
||||
|
@ -188,7 +186,6 @@ And this all despite the fact that MyClient.exe still believes that Foo lives in
|
|||
IL\_001c: ret
|
||||
} // end of method Test::Main
|
||||
```
|
||||
|
|
||||
|
||||
## Profilers
|
||||
|
||||
|
@ -199,5 +196,3 @@ This should make life easy for profilers, since they generally expect to be able
|
|||
However, type forwarding is important to understand if your profiler needs to follow metadata references directly. More generally, if your profiler is reading through metadata and expects to come across a typeDef (e.g., perhaps a metadata reference points to a type in that module, or perhaps your profiler expects certain known types to be in certain modules), then your profiler should be prepared to find an mdtExportedType instead, and to deal gracefully with it rather than doing something silly like crashing.
|
||||
|
||||
In any case, whether you think your profiler will be affected by type forwarding, be sure to test, test, test!
|
||||
|
||||
|
|
@ -52,9 +52,7 @@ The proposal for this is to "roll-backwards" starting with the "found" version.
|
|||
|
||||
#### Roll-forward uses app's TFM
|
||||
|
||||
A secondary issue with with the store's naming convention for framework. It contains a path such as:
|
||||
`\dotnet\store\x64\netcoreapp2.0\microsoft.applicationinsights\2.4.0`
|
||||
where 'netcoreapp2.0' is a "tfm" (target framework moniker). During roll-forward cases, the tfm is still the value specified in the app's runtimeconfig. The host only includes store folders that match that tfm, so it may not find packages from other deps files that were generated off a different tfm. In addition, with the advent of multiple frameworks, it makes it cumbersome to be forced to install to every tfm because multiple frameworks may use the same package, and because each package is still identified by an exact version.
|
||||
A secondary issue with with the store's naming convention for framework. It contains a path such as: `\dotnet\store\x64\netcoreapp2.0\microsoft.applicationinsights\2.4.0` where 'netcoreapp2.0' is a "tfm" (target framework moniker). During roll-forward cases, the tfm is still the value specified in the app's runtimeconfig. The host only includes store folders that match that tfm, so it may not find packages from other deps files that were generated off a different tfm. In addition, with the advent of multiple frameworks, it makes it cumbersome to be forced to install to every tfm because multiple frameworks may use the same package, and because each package is still identified by an exact version.
|
||||
|
||||
The proposal for this is to add an "any" tfm.
|
||||
|
||||
|
@ -81,8 +79,7 @@ Where "found" means the version that is being used at run time including roll-fo
|
|||
For example,
|
||||
`\dotnet\store\x64\any\microsoft.applicationinsights\2.4.0`
|
||||
|
||||
The `any` tfm would be used if the specified tfm (e.g. netcoreapp2.0) is not found:
|
||||
`\dotnet\store\x64\netcoreapp2.0\microsoft.applicationinsights\2.4.0`
|
||||
The `any` tfm would be used if the specified tfm (e.g. netcoreapp2.0) is not found: `\dotnet\store\x64\netcoreapp2.0\microsoft.applicationinsights\2.4.0`
|
||||
|
||||
_Possible risk: doesn't this make "uninstall" more difficult? Because multiple installs may write the same packages and try to remove packages that another installer created?_
|
||||
|
||||
|
|
|
@ -30,8 +30,7 @@ In the `.runtimeconfig.json` these values are defined like this:
|
|||
```
|
||||
|
||||
#### Framework name
|
||||
Each framework reference identifies the framework by its name.
|
||||
Framework names are case sensitive (since they're used as folder names even on Linux systems).
|
||||
Each framework reference identifies the framework by its name. Framework names are case sensitive (since they're used as folder names even on Linux systems).
|
||||
|
||||
#### Version
|
||||
Framework version must be a [SemVer V2](https://semver.org) valid version.
|
||||
|
@ -146,11 +145,13 @@ Pros
|
|||
|
||||
Cons
|
||||
* Testing behavior of new releases with pre-release versions is not fully possible (see below).
|
||||
* Some special cases don't work:
|
||||
One special case which would not work:
|
||||
*Component A which asks for `2.0.0 LatestMajor` is loaded first on a machine which has `3.0.0` and also `3.1.0-preview` installed. Because it's the first in the process it will resolve the runtime according to the above rules - that is prefer release version - and thus will select `3.0.0`.
|
||||
Later on component B is loaded which asks for `3.1.0-preview LatestMajor` (for example the one in active development). This load will fail since `3.0.0` is not enough to run this component.
|
||||
Loading the components in reverse order (B first and then A) will work since the `3.1.0-preview` runtime will be selected.*
|
||||
* Some special cases don't work.
|
||||
|
||||
One special case which would not work:
|
||||
*Component A which asks for `2.0.0 LatestMajor` is loaded first on a machine which has `3.0.0` and also `3.1.0-preview` installed. Because it's the first in the process it will resolve the runtime according to the above rules - that is prefer release version - and thus will select `3.0.0`.*
|
||||
|
||||
*Later on component B is loaded which asks for `3.1.0-preview LatestMajor` (for example the one in active development). This load will fail since `3.0.0` is not enough to run this component.*
|
||||
*Loading the components in reverse order (B first and then A) will work since the `3.1.0-preview` runtime will be selected.*
|
||||
|
||||
Modification to automatic roll forward to latest patch:
|
||||
Existing behavior is to find a matching framework based on the above rules and then apply roll forward to latest patch (except if `Disable` is specified). The new behavior should be:
|
||||
|
|
|
@ -7,11 +7,9 @@ Note that the exit code returned by running an application via `dotnet.exe` or `
|
|||
|
||||
* `Success` (`0`) - Operation was successful.
|
||||
|
||||
* `Success_HostAlreadyInitialized` (`0x00000001`) - Initialization was successful, but another host context is already initialized, so the returned context is "secondary". The requested context was otherwise fully compatible with the already initialized context.
|
||||
This is returned by `hostfxr_initialize_for_runtime_config` if it's called when the host is already initialized in the process. Comes from `corehost_initialize` in `hostpolicy`.
|
||||
* `Success_HostAlreadyInitialized` (`0x00000001`) - Initialization was successful, but another host context is already initialized, so the returned context is "secondary". The requested context was otherwise fully compatible with the already initialized context. This is returned by `hostfxr_initialize_for_runtime_config` if it's called when the host is already initialized in the process. Comes from `corehost_initialize` in `hostpolicy`.
|
||||
|
||||
* `Success_DifferentRuntimeProperties` (`0x00000002`) - Initialization was successful, but another host context is already initialized and the requested context specified some runtime properties which are not the same (either in value or in presence) to the already initialized context.
|
||||
This is returned by `hostfxr_initialize_for_runtime_config` if it's called when the host is already initialized in the process. Comes from `corehost_initialize` in `hostpolicy`.
|
||||
* `Success_DifferentRuntimeProperties` (`0x00000002`) - Initialization was successful, but another host context is already initialized and the requested context specified some runtime properties which are not the same (either in value or in presence) to the already initialized context. This is returned by `hostfxr_initialize_for_runtime_config` if it's called when the host is already initialized in the process. Comes from `corehost_initialize` in `hostpolicy`.
|
||||
|
||||
|
||||
### Failure error/exit codes
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# `AssemblyLoadContext` unloadability
|
||||
# `AssemblyLoadContext` unloadability
|
||||
## Goals
|
||||
* Provide a building block for unloadable plug-ins
|
||||
* Users can load an assembly and its dependencies into an unloadable `AssemblyLoadContext`.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Performance Tracing on Linux
|
||||
Performance Tracing on Linux
|
||||
============================
|
||||
|
||||
When a performance problem is encountered on Linux, these instructions can be used to gather detailed information about what was happening on the machine at the time of the performance problem.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Instructions on how to setup database
|
||||
# Instructions on how to setup database
|
||||
|
||||
## In Fedora 24 container:
|
||||
- `docker ps` shows _id of existing Fedora 24 container
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue