2

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:

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:

  1. in Visual Studio's link, there is the option /FIXED (which does exactly what I want)
  2. there is this tuturial, but most of it seems to apply to gcc under Linux only
  3. I can build the DLL without -shared and instead set --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.

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:

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).

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.

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?

Philip
  • 271
  • 1
  • 15
  • 1
    Why the downvote? Please explain in a comment, so I can improve the question. – Philip May 02 '16 at 18:29
  • You're assuming that these are not necessary. Why do you assume so? How have you checked that your assumption is not incorrect? – Kuba hasn't forgotten Monica May 02 '16 at 18:41
  • @KubaOber I thought this is what `-pie` does, create a "position independent executable". Seems to me this just doesn't apply to function pointers in the .rdata section. But exactly that is my question (I have edited it to make this more clear). – Philip May 02 '16 at 18:51
  • Why do you want to get rid of the relocations? The `-pie` option is largely ignored on Windows targets. It does exact opposite of what you want, it tells the linker to create relocations for executables (.EXE). Normally executables on Windows aren't relocatable, the relocations allow it be relocated like a DLL. Normally DLLs do have relocations, the Visual Studio option `/FIXED` creates DLLs without them, preventing the DLL from being relocated. This will prevent Windows from loading the DLL if something is already at the address the DLL must be loaded at. – Ross Ridge May 04 '16 at 04:19
  • Several options: (1.) static build (if licenses permit) - no PIC required. (2.) Compile with -fvisibility=hidden then mark symbols that need to be explicitly exported with attribute visibility default (I forget the syntax). (3.) mark the symbols you don't want to export with attribute visibility hidden. or (4.) use a linker script and map file – technosaurus May 06 '16 at 02:02

2 Answers2

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.

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).

Philip
  • 271
  • 1
  • 15
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.

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.

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.

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
}
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • What is the (minimalist) way to go? Build vtables at runtime (like CPython does) or rely on the loader for relocation (like C++ does, I think)? – Philip May 02 '16 at 19:21
  • Besides then, where exactly is the difference of `-fpic` and `-pie` on GCC? – Philip May 02 '16 at 19:24
  • @PhilipDahnen A vtable is just one way of implementing dynamic dispatch. At the end of the day, you know exactly what functions to call, so you can implement a thunk yourself. Why do you hate relocations so much? – Kuba hasn't forgotten Monica May 02 '16 at 19:34
  • @PhilipDahnen For pic vs pie see [this question](http://stackoverflow.com/q/2463150/1329652). TL;DR: PIE is like PIC, but for executables and comes at a slightly lower cost in some cases. – Kuba hasn't forgotten Monica May 02 '16 at 19:36
  • I don't hate them. I just want to know if there is a smarter way, as they incur runtime cost (as you stated). Your solution is good, but has the consequence that all functions need to be imported. – Philip May 02 '16 at 19:41
  • @PhilipDahnen That's not a big deal, the signatures must match so you can macro-ize it out. Edit: actually you can't macro-ize it out, so that's two lines per virtual method: one to declare it locally, another to add it to a switch. – Kuba hasn't forgotten Monica May 02 '16 at 19:45
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/110859/discussion-between-philip-dahnen-and-kuba-ober). – Philip May 02 '16 at 19:46