如何使用 MinGW-gcc 删除 DLL .reloc 部分?

How to get rid of DLL .reloc section using MinGW-gcc?

我正在用 C 手动构建 vtables。当从 DLL 导出时,它们会在其重定位中生成很多条目 table。
示例 objdump 输出:

Virtual Address: 00002000 Chunk size 24 (0x18) Number of fixups 8
    reloc    0 offset    0 [2000] HIGHLOW
    reloc    1 offset    4 [2004] HIGHLOW
    reloc    2 offset    8 [2008] HIGHLOW
    reloc    3 offset    c [200c] HIGHLOW
    reloc    4 offset   10 [2010] HIGHLOW
    reloc    5 offset   14 [2014] HIGHLOW
    reloc    6 offset   18 [2018] HIGHLOW
    reloc    7 offset   1c [201c] HIGHLOW

有没有办法摆脱它们,或者它们是 Windows 上的唯一方法?
这些是我到目前为止的发现:

  1. 在 Visual Studio 的 link 中,有选项 /FIXED(这正是我想要的)
  2. this教程,但大部分似乎只适用于Linux下的gcc
  3. 我可以在没有 -shared 的情况下构建 DLL,而是设置 --image-base

最后一个确实有效(没有生成 .reloc 部分),但我认为这是一个极其丑陋的 hack,因为它实际上不再是 DLL。

澄清:

我的印象是这个问题之所以被否决,是因为人们发现搬迁是一件好事。 我承认,总的来说它们很好,但我有一个非常具体的 objective。我想展示如何在 O(1) 中实现 vtables 的动态多态性,如下所示:

struct IDerived {
    union {
        IBaseA asBaseA;
        struct {
            int (*foo)(Derived this); // inherited from BaseA
            ...
        };
    };
    union {
        IBaseB asBaseB;
        struct {
            int (*bar)(Derived this); // inherited from BaseB
            ...
        };
    };
    int (*baz)(Derived this);
    ...
};

struct Derived {
    const IDerived *iface;
    void *data;
};

extern void doSthWithBaseB(BaseB x);

void doSthWithDerived(Derived x) {
    x.iface->foo(x);
    doSthWithBaseB((BaseB){ &x.iface->asBaseB, x.data }) // high-level cast
}

由于"high-level cast"只涉及指针运算,这是O(1)(特别是,没有像Java那样进行线性搜索)。

现在回到重定位:无论成本有多低,它恰好是 O(n),因为每个 class 中的每个方法都需要更新。叹息

tl;dr
微软的 GCC /FIXED 有挂件吗?如果不是,要在 PE 中设置哪些标志以实现所需的行为?

PIC 不是指与位置无关的数据。您的 code 与位置无关,但这会产生 运行 时间成本。数据部分可以在 compile/link 时间填充函数地址并没有什么魔力,因为它们在 运行 时间发生变化 - 否则 PIE 不会说 运行 时间成本开始.编译器也许可以使用某种不同类型的函数指针,它指向 PIC 函数并在调用之前得到固定,但这会在每个函数指针取消引用时产生额外的成本。因此编译器默认情况下不这样做。

您可以让 运行time 链接器完成它的工作并在您的代码加载时修复您的 vtable,或者您可以在 运行time 填充 vtable,前提是编译器不会优化这样的代码出来并给你 rdata vtable 回来。无论哪种方式,你都在做同样的事情,你不会摆脱它。

除了带有函数指针的 vtable,您可以显式 thunk 并希望开关不会用编译器生成的 vtable 实现。 C_foo(&c, ...) thunk 调用将替换 c->vtable->foo(...) 调用。

typedef enum { C_type, D_type } type_t;


typedef struct {
  type_t type;
} C;

typedef struct {
  type_t type;
} D;

// Replaces the vtable
void C_foo_impl(int);
void D_foo_impl(int);
void C_foo(C * self, int i) {
  switch (self->type) {
  case C_type: return C_foo_impl(i);
  case D_type: return D_foo_impl(i);
  default: return;
  }
}

void C_init(C * self) {
  self->type = C_type;
}

void D_init(D * self) {
  C_init((C*)self);
  self->type = D_type;
}

void test(void) {
  C c;
  C_init(&c);
  D d;
  D_init(&d);
  C_foo(&c, 10); // calls c_foo_impl
  C_foo((C*)&d, 10); // calls d_foo_impl
}

好的,我自己找到了答案:

必须在 DLL 上使用 strip -R .reloc,然后手动将 IMAGE_FILE_RELOCS_STRIPPED (0x0001) 添加到 PE header 中的 Characteristics 字段。这样就可以了。

这当然要与自定义基地址 (-Wl,--image-base=...) 一起使用 – 否则 Windows 将无法加载 DLL。
生成的模块也可能是防病毒软件的误报(因此会立即移动到容器中)。