Hi. I was looking at the IL code of a valid method with Reflector and I've run into this:
L_00a5: leave.s L_0103
Instructions with the suffix .s
are supposed to take an int8 operand, and sure enough this should be the case with Leave_S as well. However, 0x0103 is 259, which exceeds the capacity of an int8. The method somehow works, but when I read the instructions with method Mono.Reflection.Disassembler.GetInstructions
it retrieves
L_00a5: leave.s L_0003
that is, 3 instead of 259, because it's supposed to be an int8. So, my question: how is the original instruction (leave.s L_0103
) possible? I have looked at the ECMA documentation for that (Partition III: CIL Instruction Set) and I can't find anything that explains it.
Any ideas? Thanks.
EDIT #1: Ok, I'm an idiot. In the case of branch instructions the offset must be counted from the beginning of the instruction following the current instruction. I swear I read the documentation, but somehow I managed to skip that. In my defence, I'm pretty sick today. Sigh.
Thank you. (And thanks for not calling me an idiot, even though this was pretty idiotic :P)
EDIT #2: By the way, in case anyone is interested, when Mono.Reflection.Disassembler.GetInstructions
disassembles the instructions it changes the meaning of the operand in branch instructions. In particular, as it has been pointed out, the operand of a branch instruction represents the offset from the beginning of the next instruction, not from 0. However, Mono.Reflection
gives back the offset starting at 0 (which may be why I was confused; although it doesn't explain how I managed to skip part of the documentation).
An extract of MethodBodyReader.ReadOperand(Instruction instruction)
:
switch (instruction.OpCode.OperandType) {
...
case OperandType.ShortInlineBrTarget:
instruction.Operand = (sbyte) (il.ReadByte () + il.position);
break;
...
}
As you can see it adds il.position
, which is the offset (starting at 0) of the next instruction. Also, it casts to sbyte
, which is the reason I'm getting 3 instead of 259. This appears to be a bug (the offset starting from 0 may be larger than an sbyte
). I'll ask Jb Evain (the author) and report back.
EDIT #3: He hasn't answered yet but I've changed it to:
switch (instruction.OpCode.OperandType) {
...
case OperandType.ShortInlineBrTarget:
instruction.Operand = ((sbyte) il.ReadByte ()) + il.position;
break;
...
}
and it seems to have solved my problem. I cast to sbyte
to get the sign right, in case it's a backwards jump (negative offset), and then when I add il.position
(which is an int
) the result is an int
.
I'll let you know what he says anyway.
EDIT #4: I forgot to report back. The author confirms this was a bug.