In the binary form of CIL all branches and exception ranges are byte offsets into the method. In the text form they are labels: names for addresses, which ilasm turns into offsets. Labels are what make a listing readable, and the reason ildasm prints an IL_0000: label (the offset in hexadecimal) in front of the instructions that something jumps to, or in front of all of them.
Labels
A code label is an Id followed by a colon, at the place of an instruction: it names the address of the instruction that comes next. Any Id will do: IL_0000, loop, 'my label'. A label may be on a line of its own or in front of an instruction (done: ret); it must be unique in its method; and it may be used before it is defined. A data label names a .data item and has no colon (Fields and data). A code label is never used as a data label or the reverse.
Branches and switch
Every branch has a long form with a four-byte offset (br) and a short form with a one-byte offset (br.s), which reaches only 128 bytes back or 127 forward from the end of the instruction. The operand is a label; ilasm also takes a number, which it uses as the relative offset itself. The offset of a short branch must fit in one signed byte; the ilasm option /OPTIMIZE (“optimize long instructions to short”) shortens the long forms that fit.
Instructions (long form; each has an .s short form) | Branches when |
|---|---|
br | always |
brtrue, brfalse (aliases brinst, brnull, brzero) | the value on the stack is true or non-null, or false, null or zero |
beq, bne.un | the two values are equal, or not equal (or unordered) |
bge, bgt, ble, blt | the first value is greater or equal, greater, less or equal, less (signed, or ordered for floats) |
bge.un, bgt.un, ble.un, blt.un | the same comparisons unsigned, or true if the floats are unordered |
leave | always, and leaves a protected block (below) |
switch (label, label, …) | jumps to the label whose position in the list equals the unsigned value on the stack; falls through when the value is too large |
A branch must come after the instructions that put its operands on the stack, and the stack must have the same shape at the label as at the branch (the rules of the verifier, Partition III). A switch list can be empty and its items may be labels or numbers. The instruction set page lists every branch and the CIL opcode reference gives their encodings.
ldarg.0
switch (case0, case1, case2)
br.s other
case0: ldstr "zero" br.s done
case1: ldstr "one" br.s done
case2: ldstr "two"
done: ret
other: ldnull
ret
br.s other.Exception blocks
An exception block is a protected block (.try) followed by one or more handlers. Each handler is a catch for one exception type, a filter that decides at run time, a finally that runs on every exit, or a fault that runs only when an exception passes through. Every range is written in one of two ways: as a block in braces, or as a range between two labels. The first label is the first instruction of the range, the second is the instruction just after it. (The grammar also accepts numbers instead of labels in each of these places, for offsets that ildasm prints with its /RAWEH option.)
| Part | Block form | Range form |
|---|---|---|
| protected range | .try { … } | .try start to end |
| catch | catch type { … } | catch type handler start to end |
| filter | filter { … } { … } (the first block is the filter code, the second the handler); or filter label { … } | filter label handler start to end |
| finally | finally { … } | finally handler start to end |
| fault | fault { … } | fault handler start to end |
.try
{
ldstr "work"
call void [mscorlib]System.Console::WriteLine(string)
leave.s done
}
catch [mscorlib]System.FormatException
{
pop
leave.s done
}
finally
{
ldstr "cleanup"
call void [mscorlib]System.Console::WriteLine(string)
endfinally
}
done: ret
// the same try range written with labels (ildasm's style):
// .try IL_0000 to IL_0010 catch [mscorlib]System.FormatException handler IL_0010 to IL_0018
The rules the standard sets (II.19, and Partition I and III for the details):
- A protected block is entered only at its first instruction and left only with
leave(or by an exception or athrow); falling off its end is not allowed.leaveempties the stack and runs thefinallyhandlers it exits. - A catch handler starts with the exception object on the stack: use it or
popit. It ends withleave(orrethrow, which throws the exception again). - A filter block must come immediately before its handler. It ends with
endfilterafter pushing anint32answer: 1 to run the handler, 0 to go on searching. - A finally handler ends with
endfinally; a fault handler withendfault(ilasm treatsendfaultas an alias ofendfinally, which has the same encoding). Neither can be entered by a branch or by falling in. - No two handlers have the same start address, and the blocks of a method nest properly.
Not described
Known, not yet covered
- Reference: the full verifier rules for branches and exception blocks (Partition III, III.1.7 and the Partition I clauses on exception handling) are summarised, not reproduced. Whether ilasm of .NET accepts a range label that is defined after its use in
.try … to …was not tested; the standard requires the labels to be defined first.