You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Jan 23, 2023. It is now read-only.
Copy file name to clipboardExpand all lines: Documentation/botr/clr-abi.md
+4-4
Original file line number
Diff line number
Diff line change
@@ -87,7 +87,7 @@ ARM64-only: When a method returns a structure that is larger than 16 bytes the c
87
87
88
88
The convention is that any method with an InlinedCallFrame (either an IL stub or a normal method with an inlined PInvoke) saves/restores all non-volatile integer registers in its prolog/epilog respectively. This is done so that the InlinedCallFrame can just contain a return address, a stack pointer and a frame pointer. Then using just those three it can start a full stack walk using the normal RtlVirtualUnwind.
89
89
90
-
When encountering a PInvoke, the JIT will query the VM if the GC transition should be suppressed. Suppression of the GC transition is indicated by the addition of an attribute on the PInvoke definition. If the VM indicates the GC transition is to be suppressed, the PInvoke frame will be omitted in either the IL stub or inlined scenario and a GC Poll will be inserted near the unmanaged call site. If an enclosing function contains more than one inlined PInvoke but not all have requested a suppression of the GC transition a PInvoke frame will still be constructed for the other inlined PInvokes.
90
+
When encountering a PInvoke, the JIT will query the VM if the GC transition should be suppressed. Suppression of the GC transition is indicated by the addition of an attribute on the PInvoke definition. If the VM indicates the GC transition is to be suppressed, the PInvoke frame will be omitted in either the IL stub or inlined scenario and a GC Poll will be inserted near the unmanaged call site. If an enclosing function contains more than one inlined PInvoke but not all have requested a suppression of the GC transition a PInvoke frame will still be constructed for the other inlined PInvokes.
91
91
92
92
For AMD64, a method with an InlinedCallFrame must use RBP as the frame register.
93
93
@@ -147,7 +147,7 @@ For all platforms except Windows/x86, all managed EH handlers (finally, fault, f
147
147
148
148
The only way to enter a handler funclet is via a call. In the case of an exception, the call is from the VM's EH subsystem as part of exception dispatch/unwind. In the non-exceptional case, this is called local unwind or a non-local exit. In C# this is accomplished by simply falling-through/out of a try body or an explicit goto. In IL this is always accomplished via a LEAVE opcode, within a try body, targeting an IL offset outside the try body. In such cases the call is from the JITed code of the parent function.
149
149
150
-
For Windows/x86, all handlers are generated within the method body, typically in lexical order. A nested try/catch is generated completely within the EH region in which it is nested. These handlers are essentially "in-line funclets", but they do not look like normal functions: they do not have a normal prolog or epilog, although they do have special entry/exit and register conventions. Also, nested handlers are not un-nested as for funclets: the code for a nested handler is generated within the handler in which it is nested.
150
+
For Windows/x86, all handlers are generated within the method body, typically in lexical order. A nested try/catch is generated completely within the EH region in which it is nested. These handlers are essentially "in-line funclets", but they do not look like normal functions: they do not have a normal prolog or epilog, although they do have special entry/exit and register conventions. Also, nested handlers are not un-nested as for funclets: the code for a nested handler is generated within the handler in which it is nested.
151
151
152
152
## Cloned finallys
153
153
@@ -421,7 +421,7 @@ The ShadowSP slots are required to live in a very particular location, reported
421
421
4. 1 slot for CORINFO_GENERICS_CTXT_FROM_PARAMTYPEARG -- assumed for any function with EH, to avoid adding a flag to the GC info about whether it exists or not.
422
422
5. ShadowSP slots
423
423
424
-
(note, these don't have to be in this order for this calculation, but they possibly do need to be in this order for other calculations.) See also `GetEndShadowSPSlotsOffset()`.
424
+
(note, these don't have to be in this order for this calculation, but they possibly do need to be in this order for other calculations.) See also `GetEndShadowSPSlotsOffset()`.
425
425
426
426
The VM walks the ShadowSP slots in the function `GetHandlerFrameInfo()`, and sets it in various functions such as `EECodeManager::FixContext()`.
427
427
@@ -671,7 +671,7 @@ The general rules outlined in the System V x86_64 ABI (described at http://www.x
671
671
1. The hidden argument for by-value passed structs is always after the "this" parameter (if there is one). This is a difference with the System V ABI and affects only the internal JIT calling conventions. For PInvoke calls the hidden argument is always the first parameter since there is no "this" parameter in this case.
672
672
2. Managed structs that have no fields are always passed by-value on the stack.
673
673
3. The JIT proactively generates frame register frames (with `RBP` as a frame register) in order to aid the native OS tooling for stack unwinding and the like.
674
-
4. All the other internal VM contracts for PInvoke, EH, and generic support remains in place. Please see the relevant sections above for more details. Note, however, that the registers used are different on System V due to the different calling convention. For example, the integer argument registers are, in order, RDI, RSI, RDX, RCX, R8, and R9. Thus, where the first argument (typically, the "this" pointer) on Windows AMD64 goes in RCX, on System V it goes in RDI, and so forth.
674
+
4. All the other internal VM contracts for PInvoke, EH, and generic support remains in place. Please see the relevant sections above for more details. Note, however, that the registers used are different on System V due to the different calling convention. For example, the integer argument registers are, in order, RDI, RSI, RDX, RCX, R8, and R9. Thus, where the first argument (typically, the "this" pointer) on Windows AMD64 goes in RCX, on System V it goes in RDI, and so forth.
675
675
5. Structs with explicit layout are always passed by value on the stack.
676
676
6. The following table describes register usage according to the System V x86_64 ABI
Copy file name to clipboardExpand all lines: Documentation/botr/exceptions.md
+15-15
Original file line number
Diff line number
Diff line change
@@ -26,12 +26,12 @@ EX_TRY
26
26
The basic macros are, of course, EX_TRY / EX_CATCH / EX_END_CATCH, and in use they look like this:
27
27
28
28
EX_TRY
29
-
// Call some function. Maybe it will throw an exception.
29
+
// Call some function. Maybe it will throw an exception.
30
30
Bar();
31
-
EX_CATCH
32
-
// If we're here, something failed.
33
-
m_finalDisposition = terminallyHopeless;
34
-
EX_END_CATCH(RethrowTransientExceptions)
31
+
EX_CATCH
32
+
// If we're here, something failed.
33
+
m_finalDisposition = terminallyHopeless;
34
+
EX_END_CATCH(RethrowTransientExceptions)
35
35
36
36
The EX_TRY macro simply introduces the try block, and is much like the C++ "try", except that it also includes an opening brace, "{".
37
37
@@ -44,12 +44,12 @@ And here is the big difference from C++ exceptions: the CLR developer doesn't ge
44
44
45
45
It bears repeating that the EX_CATCH macro catches everything. This behaviour is frequently not what a function needs. The next two sections discuss more about how to deal with exceptions that shouldn't have been caught.
46
46
47
-
GET_EXCEPTION() & GET_THROWABLE()
47
+
GET_EXCEPTION() & GET_THROWABLE()
48
48
---------------------------------
49
49
50
50
How, then, does a CLR developer discover just what has been caught, and determine what to do? There are several options, depending on just what the requirement is.
51
51
52
-
First, whatever the (C++) exception that is caught, it will be delivered as an instance of some class derived from the global Exception class. Some of these derived classes are pretty obvious, like OutOfMemoryException. Some are somewhat domain specific, like EETypeLoadException. And some of these are just wrapper classes around another system's exceptions, like CLRException (has an OBJECTHANDLE to reference any managed exception) or HRException (wraps an HRESULT). If the original exception was not derived from Exception, the macros will wrap it up in something that is. (Note that all of these exceptions are system-provided and well known. _New exception classes shouldn't be added without involving the Core Execution Engine Team!_)
52
+
First, whatever the (C++) exception that is caught, it will be delivered as an instance of some class derived from the global Exception class. Some of these derived classes are pretty obvious, like OutOfMemoryException. Some are somewhat domain specific, like EETypeLoadException. And some of these are just wrapper classes around another system's exceptions, like CLRException (has an OBJECTHANDLE to reference any managed exception) or HRException (wraps an HRESULT). If the original exception was not derived from Exception, the macros will wrap it up in something that is. (Note that all of these exceptions are system-provided and well known. _New exception classes shouldn't be added without involving the Core Execution Engine Team!_)
53
53
54
54
Next, there is always an HRESULT associated with a CLR internal exception. Sometimes, as with HRException, the value came from some COM source, but internal errors and Win32 api failures also have HRESULTS.
In the example above, "RethrowTransientExceptions" is an argument to the EX_END_CATCH macro; it is one of three pre-defined macros that can be thought of "exception disposition". Here are the macros, and their meanings:
88
88
89
-
-_SwallowAllExceptions_: This is aptly named, and very simple. As the name suggests, it swallows everything. While simple and appealing, this is often not the right thing to do.
89
+
-_SwallowAllExceptions_: This is aptly named, and very simple. As the name suggests, it swallows everything. While simple and appealing, this is often not the right thing to do.
90
90
-_RethrowTerminalExceptions_. A better name would be "RethrowThreadAbort", which is what this macro does.
91
91
-_RethrowTransientExceptions_. The best definition of a "transient" exception is one that might not occur if tried again, possibly in a different context. These are the transient exceptions:
92
92
- COR_E_THREADABORTED
@@ -119,7 +119,7 @@ Sometimes all that is needed is the HRESULT corresponding to an exception, parti
119
119
120
120
_However, while very tempting, it is not always correct_. The EX_CATCH_HRESULT catches all exceptions, saves the HRESULT, and swallows the exception. So, unless that exception swallowing is what the function really needs, EX_CATCH_HRESULT is not appropriate.
121
121
122
-
EX_RETHROW
122
+
EX_RETHROW
123
123
----------
124
124
125
125
As noted above, the exception macros catch all exceptions; the only way to catch a specific exception is to catch all, and rethrow all but the one(s) of interest. So, if, after an exception is caught, examined, possibly logged, and so forth, it shouldn't be caught, it may be re-thrown. EX_RETHROW will re-raise the same exception.
@@ -169,7 +169,7 @@ There are some pre-defined convenience variations:
169
169
COMPlusThrowOOM();
170
170
------------------
171
171
172
-
Defers to ThrowOutOfMemory(), which throws the C++ OOM exception. This will throw a pre-allocated exception, to avoid the problem of being out of memory trying to throw an out of memory exception!
172
+
Defers to ThrowOutOfMemory(), which throws the C++ OOM exception. This will throw a pre-allocated exception, to avoid the problem of being out of memory trying to throw an out of memory exception!
173
173
174
174
When getting the managed exception object for this exception, the runtime will first try to allocate a new managed object <sup>[1]</sup>, and if that fails, will return a pre-allocated, shared, global out of memory exception object.
There are a number of overloads, in case you have an IErrorInfo, etc. There is some surprisingly complicated code to figure out what kind of exception corresponds to a particular HRESULT.
182
182
183
-
COMPlusThrowWin32(); / COMPlusThrowWin32(hr);
183
+
COMPlusThrowWin32(); / COMPlusThrowWin32(hr);
184
184
---------------------------------------------
185
185
186
186
Basically throws an HRESULT_FROM_WIN32(GetLastError())
187
187
188
-
COMPlusThrowSO();
188
+
COMPlusThrowSO();
189
189
-----------------
190
190
191
191
Throws a Stack Overflow (SO) Exception. Note that this is not a hard SO, but rather an exception we throw when proceeding might lead to a hard SO.
192
192
193
193
Like OOM, this throws a pre-allocated C++ SO exception object. Unlike OOM, when retrieving the managed object, the runtime always returns the pre-allocated, shared, global stack overflow exception object.
194
194
195
-
COMPlusThrowArgumentNull()
195
+
COMPlusThrowArgumentNull()
196
196
--------------------------
197
197
198
198
A helper for throwing an "argument foo must not be null" exception.
0 commit comments