如何使用MinGW-gcc摆脱DLL .reloc部分?

时间:2023-02-05 15:45:07

I'm building vtables by hand in C. When exported from a DLL, they generate a lot of entries in its relocation table.
Sample objdump output:

我正在用C手工构建vtables。从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

Is there any way to get rid of them, or are they the only way on Windows?
These are my findings so far:

有没有办法摆脱它们,还是它们是Windows上的唯一途径?以下是我的发现:

  1. in Visual Studio's link, there is the option /FIXED (which does exactly what I want)
  2. 在Visual Studio的链接中,有选项/ FIXED(它完全符合我的要求)

  3. there is this tuturial, but most of it seems to apply to gcc under Linux only
  4. 有这种tuturial,但大多数似乎只适用于Linux下的gcc

  5. I can build the DLL without -shared and instead set --image-base
  6. 我可以在没有-shared的情况下构建DLL,而是设置--image-base

The last one works indeed (no .reloc section is generated), but I consider this an extreme ugly hack, because then it's actually no DLL anymore.

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

Clarification:

I get the impression that this question is only downvoted because people find relocations are a good thing. I admit, they are good in general but I have a very specific objective. I want to show how dynamic polymorphism with vtables can be achieved in O(1), like so:

我得到的印象是,这个问题只是因为人们发现重新安置是一件好事而被低估了。我承认,他们一般都很好,但我有一个非常具体的目标。我想说明如何在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
}

As the "high-level cast" only involves pointer arithmetic, this is O(1) (especially, no linear search is done like in Java).

由于“高级转换”仅涉及指针算术,因此这是O(1)(特别是,没有像Java那样进行线性搜索)。

Now back to relocations: No matter how low the cost is, it happens to be O(n), since every method in every class needs to be updated. Sigh.

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

tl;dr
Is there a pendant to Microsoft's /FIXED for GCC? If not, what flags are to be set in the PE to achieve the desired behaviour?

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

2 个解决方案

#1


2  

Okay, I found an answer myself:

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

One has to use strip -R .reloc on the DLL and then manually add IMAGE_FILE_RELOCS_STRIPPED (0x0001) to the Characteristics field in the PE header. That will do it.

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

This is to be used with a custom base address (-Wl,--image-base=...) of course – otherwise Windows won't be able to load the DLL.
The resulting module may also be a false positive for antivirus software (and thus be moved to the container right away).

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

#2


1  

PIC doesn't mean position-independent data. Your code is position independent, but that incurs run-time costs. There's no magic by which the data section can be populated with function addresses at compile/link time, since they vary at runtime - otherwise PIE wouldn't have said runtime costs to begin with. The compiler perhaps could use some different kind of a function pointer that points to PIC functions and gets fixed-up before invocation, but that would incur an extra cost on each function pointer dereference. Thus the compilers don't do that by default.

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

You can let the runtime linker do its job and fix up your vtables when your code gets loaded, or you can populate the vtable at runtime iff the compiler won't optimize such code out and give you the rdata vtable back. Either way, you're doing the same thing and you won't get rid of it.

您可以让运行时链接程序完成其工作并在加载代码时修复vtable,或者如果编译器不优化此类代码并返回rdata vtable,则可以在运行时填充vtable。无论哪种方式,你都在做同样的事情,你不会摆脱它。

Instead of a vtable with function pointers, you can explicitly thunk and hope that the switch won't be implemented with a compiler-generated vtable. The C_foo(&c, ...) thunk call would then replace the c->vtable->foo(...) call.

您可以明确地使用函数指针,而不是带有函数指针的vtable,并希望切换器不会使用编译器生成的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
}

#1


2  

Okay, I found an answer myself:

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

One has to use strip -R .reloc on the DLL and then manually add IMAGE_FILE_RELOCS_STRIPPED (0x0001) to the Characteristics field in the PE header. That will do it.

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

This is to be used with a custom base address (-Wl,--image-base=...) of course – otherwise Windows won't be able to load the DLL.
The resulting module may also be a false positive for antivirus software (and thus be moved to the container right away).

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

#2


1  

PIC doesn't mean position-independent data. Your code is position independent, but that incurs run-time costs. There's no magic by which the data section can be populated with function addresses at compile/link time, since they vary at runtime - otherwise PIE wouldn't have said runtime costs to begin with. The compiler perhaps could use some different kind of a function pointer that points to PIC functions and gets fixed-up before invocation, but that would incur an extra cost on each function pointer dereference. Thus the compilers don't do that by default.

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

You can let the runtime linker do its job and fix up your vtables when your code gets loaded, or you can populate the vtable at runtime iff the compiler won't optimize such code out and give you the rdata vtable back. Either way, you're doing the same thing and you won't get rid of it.

您可以让运行时链接程序完成其工作并在加载代码时修复vtable,或者如果编译器不优化此类代码并返回rdata vtable,则可以在运行时填充vtable。无论哪种方式,你都在做同样的事情,你不会摆脱它。

Instead of a vtable with function pointers, you can explicitly thunk and hope that the switch won't be implemented with a compiler-generated vtable. The C_foo(&c, ...) thunk call would then replace the c->vtable->foo(...) call.

您可以明确地使用函数指针,而不是带有函数指针的vtable,并希望切换器不会使用编译器生成的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
}