如何使用 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 上的唯一方法?
这些是我到目前为止的发现:
- 在 Visual Studio 的
link
中,有选项 /FIXED
(这正是我想要的)
- 有this教程,但大部分似乎只适用于Linux下的
gcc
- 我可以在没有
-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。
生成的模块也可能是防病毒软件的误报(因此会立即移动到容器中)。
我正在用 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 上的唯一方法?
这些是我到目前为止的发现:
- 在 Visual Studio 的
link
中,有选项/FIXED
(这正是我想要的) - 有this教程,但大部分似乎只适用于Linux下的
gcc
- 我可以在没有
-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。
生成的模块也可能是防病毒软件的误报(因此会立即移动到容器中)。