Introduction
A method body is the code of one method and what the runtime needs to run it. The MethodDef table gives its position as an RVA (a relative virtual
address, mapped to a file offset through the section table), and a method with no body (abstract, or implemented by the runtime or by native code) has RVA 0. A body
has up to four parts: a header, the code, some padding, and optional
extra data sections. The layout is ECMA-335 partition II, section 25.4. A method implemented in native code (a C++/CLI or
mixed-mode image) also has an RVA, but it points at machine code, not at a body of this format; the ImplFlags column says which (a code type other than IL).
The header: tiny or fat
The two low bits of the first byte tell the two headers apart: 10 (2) is tiny and 11 (3) is fat. Any other value is not a method body,
and the explorer says so instead of reading on.
Tiny header
One byte. The low two bits are 2; the upper six bits are the size of the code in bytes, so a tiny body holds at most 63 bytes of code. A tiny body has no local
variables, no exception clauses and no extra sections, and its maximum stack depth is taken to be 8. A method that only returns is 06 2A: header
06 is size 1, format 2; 2A is ret.
Fat header
Twelve bytes, used for everything a tiny header cannot say: more than 63 bytes of code, locals, a stack deeper than 8, or exception clauses.
| Offset | Size | Field | Meaning |
|---|---|---|---|
| 0 | 2 | Flags and header size | The low twelve bits are flags, the high four bits are the size of the header in 4-byte units (3). The flags are below. |
| 2 | 2 | MaxStack | The largest number of items the evaluation stack holds at once. |
| 4 | 4 | CodeSize | The length of the code in bytes. |
| 8 | 4 | LocalVarSigTok | The token of a StandAloneSig row (table 0x11) whose blob lists the local variables, or 0 for none. |
| Flag | Name | Meaning |
|---|---|---|
0x0003 | FatFormat | The low two bits: 3 for the fat header (2 would be the tiny one). |
0x0008 | MoreSects | Extra data sections follow the code. |
0x0010 | InitLocals | The runtime sets every local variable to zero (or null) before the method starts. C# sets it for nearly every method. |
Example, from a hand-written test method with locals and four exception clauses: 1B 30 02 00 1D 00 00 00 03 00 00 11. The first two bytes are
0x301B: flags 0x01B (fat, MoreSects, InitLocals) and header size 3. Max stack is 2, the code is 29 (0x1D) bytes, and the local
variable signature is token 0x11000003, row 3 of StandAloneSig.
The local variable signature is a signature of kind 7: a count, then one type per local; a local can be marked
pinned or by-reference. The instructions refer to locals by number (ldloc.0, stloc.s 5).
The code
The code follows the header and is exactly CodeSize bytes: the instructions, one after another, with no padding between them. See the overview for how an instruction is encoded and the opcode map for the opcodes. Branch targets and exception ranges are offsets from the first byte of the code.
Extra data sections: exception clauses
If MoreSects is set, the sections start at the next 4-byte boundary after the code (counted from the start of the body, which a conforming file places on a 4-byte boundary), so up to three zero bytes of padding sit between the code and the first section. Each section begins with a kind byte:
| Bit | Name | Meaning |
|---|---|---|
0x01 | EHTable | The section holds exception handling clauses. |
0x02 | OptILTable | An optional IL table (obsolete; no compiler writes it). |
0x40 | FatFormat | The section is in the fat form (24-byte clauses and a 3-byte size). |
0x80 | MoreSects | Another section follows this one. |
The section header is 4 bytes: the kind, then the size of the whole section including this header (one byte in the small form, three bytes in the fat form; in the small form two unused bytes follow). The clauses follow, 12 bytes each in the small form and 24 in the fat form, so the size is 4 plus 12 or 24 times the number of clauses. A compiler uses the fat form when any offset or length of a clause does not fit the small fields (offsets above 65,535 or lengths above 255).
| Field | Small form | Fat form | Meaning |
|---|---|---|---|
| Flags | u16 | u32 | The kind of clause: 0 catch, 1 filter, 2 finally, 4 fault. |
| TryOffset | u16 | u32 | Where the protected block starts, in bytes from the start of the code. |
| TryLength | u8 | u32 | The length of the protected block. |
| HandlerOffset | u16 | u32 | Where the handler starts. |
| HandlerLength | u8 | u32 | The length of the handler. |
| ClassToken / FilterOffset | u32 | u32 | For a catch, the type it catches (a TypeDef, TypeRef or TypeSpec token); for a filter, the offset of the filter code; otherwise 0. |
The four kinds
- catch – the handler runs if an exception of the clause’s type (or a type derived from it) is thrown in the try block. The exception object is on the stack when the handler starts.
- filter – instead of a type, the clause has filter code that starts at the filter offset and ends where the handler starts. The filter runs when an exception is thrown in the try block, sees the exception on the stack, and ends with
endfilterhaving left a number: nonzero means the handler runs. - finally – the handler runs whenever control leaves the try block, by
leaveor by an exception, and ends withendfinally. - fault – like finally, but only when the try block is left by an exception.
The ranges are half-open: a try block that starts at 0 and is 6 long covers offsets 0 to 5, and a handler that starts at 6 is the next instruction. Each offset
and each end of a range must be the start of an instruction. The specification asks that clauses are listed so that a clause inside another comes first. Control
may leave a protected block or a handler only with leave (or by endfinally, endfilter, throw or rethrow), and the
stack is empty at the start of a try block; see exceptions.
Example: the section of the test method above is 01 34 00 00 (an EH table, 52 bytes: four clauses), and its first clause is
00 00 00 00 06 06 00 03 04 00 00 01: flags 0 (catch), the try block at offset 0 and 6 long, the handler at offset 6 and 3 long, and the type token
0x01000004, row 4 of TypeRef.
What breaks the rules
Obfuscators and damaged files break these rules, and the explorer reports each case on the body instead of refusing the file: a body that begins outside any
section or runs past the end of the file (the code is shown as far as the file has it); a fat header that says it is not 12 bytes long or sets flag bits nobody
defines; a code size of 0; an extra section whose size is not 4 plus a whole number of clauses; a clause with unknown flags; a clause whose try block or handler runs
past the code or does not start at an instruction; a filter that starts after its handler; a catch token that is not a type token; a local variable signature token
that is not a StandAloneSig row. The code itself is checked as well: see the overview for branch targets.
Not described
Known, not yet covered
- Max stack check: the viewer shows the MaxStack field but does not compute the depth the code actually reaches.
- ReadyToRun and NGEN images: the precompiled native code of a method is not a body of this format and is not decoded.
Unknown
- How the runtime handles a body whose padding between the code and the sections is not zero, or whose sections overlap the next body.
Links
Developers
- MethodBodyBlock – System.Reflection.Metadata’s reader for a body: the one these pages were checked against (.NET)
Reference
- ECMA-335 – partition II, section 25.4, method headers and exception handling data
- .NET tables – the MethodDef and StandAloneSig tables the header refers to