【问题标题】:How to get rid of DLL .reloc section using MinGW-gcc?如何使用 MinGW-gcc 摆脱 DLL .reloc 部分?
【发布时间】:2016-05-02 17:54:59
【问题描述】:

我在 C 中手动构建 vtable。当从 DLL 导出时,它们会在其重定位表中生成大量条目。
示例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 tuturial,但大部分似乎仅适用于Linux 下的gcc
  3. 我可以在没有-shared 的情况下构建DLL,而是设置--image-base

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

澄清

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

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
}

由于“高级转换”只涉及指针算术,所以这是 O(1)(尤其是不像 Java 中那样进行线性搜索)。

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

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

【问题讨论】:

  • 为什么投反对票?请在评论中解释,以便我改进问题。
  • 您假设这些不是必需的。你为什么这么认为?您如何检查您的假设是否正确?
  • @KubaOber 我认为这就是-pie 所做的,创建一个“与位置无关的可执行文件”。在我看来,这不适用于 .rdata 部分中的函数指针。但这正是我的问题(我已经对其进行了编辑以使其更清楚)。
  • 为什么要摆脱搬迁?在 Windows 目标上,-pie 选项在很大程度上被忽略了。它与您想要的完全相反,它告诉链接器为可执行文件(.EXE)创建重定位。通常 Windows 上的可执行文件不可重定位,重定位允许它像 DLL 一样重定位。通常 DLL 确实有重定位,Visual Studio 选项/FIXED 创建没有它们的 DLL,防止 DLL 被重定位。如果某些内容已经位于必须加载 DLL 的地址,这将阻止 Windows 加载 DLL。
  • 几个选项:(1.) 静态构建(如果许可证允许)- 不需要 PIC。 (2.) 使用 -fvisibility=hidden 编译,然后标记需要使用属性可见性默认值显式导出的符号(我忘记了语法)。 (3.) 标记您不想导出的符号,隐藏属性可见性。或 (4.) 使用链接器脚本和映射文件

标签: c gcc mingw portable-executable relocation


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

    PIC 并不意味着与位置无关的数据。您的 代码 与位置无关,但这会产生运行时成本。在编译/链接时可以用函数地址填充数据部分并没有什么魔力,因为它们在运行时会有所不同 - 否则 PIE 不会一开始就说运行时成本。编译器也许可以使用某种不同类型的函数指针,它指向 PIC 函数并在调用之前得到修复,但这会在每个函数指针取消引用上产生额外的成本。因此编译器默认不会这样做。

    您可以让运行时链接器完成其工作并在加载代码时修复您的 vtable,或者您可以在运行时填充 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
    }
    

    【讨论】:

    • 什么是(极简主义)路要走?在运行时构建 vtables(就像 CPython 一样)或依赖加载器进行重定位(我认为像 C++ 那样)?
    • 除此之外,GCC 上-fpic-pie 的区别到底在哪里?
    • @PhilipDahnen vtable 只是实现动态调度的一种方式。归根结底,您确切知道要调用哪些函数,因此您可以自己实现一个 thunk。你为什么这么讨厌搬家?
    • @PhilipDahnen 对于图片与馅饼,请参阅this question。 TL;DR:PIE 类似于 PIC,但用于可执行文件,并且在某些情况下成本略低。
    • 我不讨厌他们。我只想知道是否有更聪明的方法,因为它们会产生运行时成本(如您所说)。您的解决方案很好,但结果是所有功能都需要导入。
    猜你喜欢
    • 1970-01-01
    • 2019-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多