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

Difference in object alignment between MIPS and x86_64

本文关键字:对象 对齐 之间 x86 MIPS      更新时间:2023-10-16

我有一个二进制对象,它是使用 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

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

希望使两个结构的二进制布局与继承相匹配并拥有虚拟方法跨不同的字节序,在我看来就像一个失败的原因(我什至不知道你是如何设法使fwrite/fread序列化即使在相同的架构上工作的 - 覆盖 vtable 地址是灾难的秘诀 - 即使在"正常"架构上,也没有什么能保证你它们将位于同一地址中,即使跨完全相同二进制文件的多次运行也是如此)。

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

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

现在您已经有了二进制布局,请编写一些独立于体系结构的反序列化代码。假设您发现您发现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 版本,您也很快就会需要此代码。

顺便说一句,此代码的生成可能是自动化的,因为它是调试信息中可用的所有数据;因此,如果您有许多这种犯罪序列化格式的结构,则可以半自动生成反序列化代码(并将它们移动到更合理的位置以备将来使用)。

逆向工程:

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

最后,处理字节序。