为什么`OpCode.Value`有“错误的”字节序?

时间:2012-08-18 00:07:53

标签: .net endianness cil reflection.emit opcode

事实:

  1. CIL指令rethrow的操作码的正确编码是双字节序列FE 1A

  2. OpCodes.Rethrow.Value(类型short)在我的小端机器上有值0xFE1A

  3. 转换为/从字节序列转换时,
  4. BitConverter尊重机器的字节顺序。

  5. 在我的小端机器上,BitConverter.GetBytes(OpCodes.Rethrow.Value)会生成字节序列1A FE

  6. 这意味着,使用OpCode.Value在小端机器上序列化BitConverter不会产生正确的操作码编码;字节顺序颠倒过来。

    问题:

    • 是否记录了OpCode.Value的字节顺序(如果是,在哪里?),还是“实施细节”?

    • 上面的big-endian机器上的步骤4是否也会导致错误的字节排序?也就是说,在大端机器上OpCodes.Rethrow.Value0x1AFE吗?

3 个答案:

答案 0 :(得分:3)

Value属性在参考源中如下所示:

public short Value
{
    get
    {
        if (m_size == 2)
            return (short) (m_s1 << 8 | m_s2);
        return (short) m_s2;
    }
}

当然看起来完全正确,m_s2始终是最不重要的字节。看ILGenerator:

    internal void InternalEmit(OpCode opcode)
    {
        if (opcode.m_size == 1)
        {
            m_ILStream[m_length++] = opcode.m_s2;
        }
        else
        {
            m_ILStream[m_length++] = opcode.m_s1;
            m_ILStream[m_length++] = opcode.m_s2;
        }

        UpdateStackSize(opcode, opcode.StackChange());

    }

你想要的是什么,首先发出0xfe字节。

因此框架代码小心地避免依赖于endian-ness。 CIL没有endian-ness依赖,没有可变长度数据。对于文本文件,utf-8编码,x86核心机器代码指令为True。一个CIL。因此,如果您将可变长度数据转换为值(如Value属性getter所做的那样),则该代码不可避免 进行从非endian-ness数据到endian的转换 - 数据。这不可避免地让全世界的一半人感到不安,因为他们认为这是错误的方式。并且100%的程序员遇到了它。

可能最好的方法是像框架那样做,并尽可能快地恢复m_s1和m_s2,使用您自己的操作码类型。容易做到:

foo.m_s1 = opc.Value >> 8;
foo.m_s2 = opc.Value & 0xff;
foo.m_size = opc.Size;

没有依赖于endian-ness的依赖。

答案 1 :(得分:1)

我得出的结论是基于OpCode.Value属性序列化操作码表示,即:

OpCode someOpCode = …;
byte[] someOpCodeEncoding = BitConverter.GetBytes(someOpCode.Value);

是一个坏主意,但不是因为BitConverter.GetBytes(short)的使用,其行为已被充分记录。主要罪魁祸首是OpCode.Value属性,whose documentation在两个方面含糊不清:

  1. 它声明此属性包含“立即操作数的值”,它可能会也可能不会引用操作码的编码;该术语不会出现在CLI规范中的任何位置。

  2. 即使我们假设它 实际上包含操作码的编码,文档也没有说明字节顺序。 (在byte[]short之间进行转换时,字节顺序会起作用。)

  3. 为什么我的论点基于MSDN文档而不是CLI标准?因为System.Reflection.Emit不是CLI标准定义的反射库的一部分。出于这个原因,我认为the MSDN reference documentation for this namespace与官方规范一样接近是相当安全的。 (但与@Hans Passant的答案不同,我不会更进一步,并声称参考源以任何方式都是规范。)

    <强>结论:

    有两种方法可以输出给定OpCode对象的操作码编码:

    • 继续使用System.Reflection.Emit功能并使用ILGenerator.Emit(someOpCode)。在某些情况下,这可能过于严格。

    • 在操作码编码(即byte[]序列)和各种OpCode对象之间创建您自己的映射。

答案 2 :(得分:0)

尝试:

var yourStream = MemoryStream();
var writer = new System.IO.BinaryWriter(yourStream);
writer.Write(OpCodes.Rethrow.Value);

您不需要担心字节顺序,因为BinaryWriter(或读取器)将为您处理实现细节。我怀疑你得到“错误”字节顺序的原因是当你已经解码为小端时你在OpCode值上应用BitConverter,并再次应用BitConverter.GetShort()调用将反转字节订单,给你“错误”的结果。