MIPS和x86_64之间对象对齐的差异

时间:2018-03-15 16:55:01

标签: c++ mips x86-64 abi

我有一个使用MIPSpro编译器在SGI 64位机器上生成的二进制对象。我试图在运行RHEL 6.7的64位x86_64机器上读取这个二进制对象。对象的结构类似于

class A {
  public:
    A(){
      a_ = 1;
    }
    A(int a){
      a_ = a;
    }
    virtual ~A();
  protected:
    int a_;
};
class B : public A {
  public:
   // Constructors, methods, etc
    B(double b, int a){ 
      b_ = b;
      a_ = a;
    }
    virtual ~B();
  private:
    double b_;
};
A::~A(){}
B::~B(){}

读取二进制文件后,交换字节(由于字节序),我发现b是正确的,但a未对齐,表明数据与我当前的构建不对齐。

我有两个问题。首先,MIPS Pro编译器如何调整其字段以及它与gcc的方式有何不同。我对继承类的情况感兴趣。其次,gcc或C ++中是否有一个选项可以强制对齐与MIPS的方式相同?

更新1 : 为了进一步说明,代码是在MIPS ABI n64上编译的。我可以访问原始的C ++源代码,但我不能在MIPS机器上更改它。我被限制在x86_64上读取二进制文件。

更新2 : 我在两台机器上的两个类中添加virtual析构函数之前和之后都运行了sizeof命令 在MIPS和x86_64上,虚拟指令之前的输出是

size of class A: 4
size of class B: 16

添加virtual方法后,在SGI MIPS上输出

size of class A: 8
size of class B: 16

和x86-64 Linux:

size of class A: 16
size of class B: 24

看起来在这些机器上处理虚拟方法(或者只是方法一般吗?)的方式是不同的。任何想法为什么或如何解决这个问题?

2 个答案:

答案 0 :(得分:7)

希望使两个结构的二进制布局与继承 具有虚拟方法匹配 跨越不同的字节序看起来像是一个迷失的原因(我甚至不知道你是如何设法制作的fwrite / fread甚至在同一架构上进行序列化工作 - 覆盖vtable地址是灾难的一种方法 - 即使在"普通"架构上也没有任何保证他们将位于同一地址甚至跨越完全相同的二进制的多次运行。

现在,如果这个序列化格式已经写好,你必须处理它,我完全避免"匹配二进制布局"办法;你会生气,得到一个非常脆弱的结果。

相反,首先要一次找出源数据的确切二进制布局;您可以使用offsetof轻松完成MIPS计算机上的所有成员,甚至只需打印每个成员的地址并计算相关差异。

现在您已经拥有二进制布局,请编写一些与架构无关的反序列化代码。让我们说你发现你发现A是由:

组成的
  • 0x00:vptr(8字节);
  • 0x08:a_(4字节);
  • 0x0c :(填充)(4字节)

B由以下内容组成:

  • 0x00:vptr(8字节);
  • 0x08:A::a_(4字节);
  • 0x0c :(填充)(4字节);
  • 0x10:b_(8字节)。

然后您将写出在给定结构中手动反序列化 这些字段的代码。例如:

typedef unsigned char byte;

uint32_t read_u32_be(const byte *buf) {
    return uint32_t(buf[0])<<24 |
           uint32_t(buf[1])<<16 |
           uint32_t(buf[2])<<8  |
           uint32_t(buf[3]);
}

int32_t read_i32_be(const byte *buf) {
    // assume 2's complement in unsigned -> signed conversion
    return read_u32_be(buf);
}

double read_f64_be(const byte *buf) {
    static_assert(sizeof(double)==8);
    double ret;
    std::reverse_copy(buf, buf+8, (byte*)&ret);
    return ret;
}

void read_A(const byte *buf, A& t) {
    t.a_ = read_i32_be(buf+8);
}

void read_B(const uint8_t *buf, B& t) {
    read_A(buf, t);
    t.b_ = read_f64_be(buf+0x10);
}

请注意,这不是浪费精力,因为如果您碰巧更改编译器,编译设置或可能影响类的二进制布局的任何其他内容,即使是MIPS版本,您很快就会需要此代码

顺便说一下,这段代码的生成可能会自动化,如it's all data that is available in the debug information;所以,如果你有这种犯罪序列化格式的许多结构,你可以半自动生成反序列化代码(并将它们转移到将来更健全的东西)。

答案 1 :(得分:1)

逆向工程:

将一些不同的对象实例写入多个文件中。
使用十六进制编辑器查找差异。
至少你得到每个值的二进制文件中的位置。

最后,处理endianess。