How do I force gcc to call a function directly in PIC code?
Asked Answered
R

2

8

Consider the following function:

extern void test1(void);
extern void test2(void) {
    test1();
}

This is the code gcc generates without -fpic on amd64 Linux:

test2:
    jmp test1

When I compile with -fpic , gcc explicitly calls through the PLT to enable symbol interposition:

test2:
    jmp test1@PLT

This however is not strictly needed for position independent code and could be left out if I don't want to support. If necessary, the linker rewrites the jump target to the PLT symbol anyway.

How can I, without changing the source code and without making the compiled code unsuitable for a shared library, make function calls go directly to their targets instead of going explicitly through the PLT?

Roveover answered 1/4, 2016 at 10:41 Comment(5)
Do you mean for calls within your library / executable? That should be possible somehow, maybe by defining a private alias or something. But for calls to functions in libraries that you only dynamically link with, I'm not sure the runtime linker can resolve such references.Noonberg
@PeterCordes Without -fpic, the linker automatically rewrites references to symbols to PLT references where appropriate/required. That's the behaviour I want, i.e. the compiler generating normal calls to all functions and the linker rewriting the calls that go to another shared object.Roveover
Does -fno-semantic-interposition do what you want? See also #34103489. Is this question a duplicate of either of those?Noonberg
@PeterCordes Doesn't seem to make a difference but I keep that option in mind.Roveover
Future readers: see also #10849808 for trying to avoid the PLT when calling across shared-object boundaries. (e.g. from a program into a library).Noonberg
G
3

If you declare test1() hidden (__attribute__((__visibility__("hidden"))), the jump will be direct.

Now test1() may not be defined in its source translation unit as hidden, but I believe no harm should come from that discrepancy except the C language guarantee that &test1 == &test1 might be broken for you at runtime if one of the pointers was obtained via a hidden reference and one via a public one (the public reference might have been interposed via preloading or a DSO that came before the current one in the lookup scope, while the hidden reference (which results in direct jumps) effective prevents any kind of interposition)

A more proper way to deal with this would be to define two names for test1()—a public name and a private/hidden name.

In gcc and clang, this can be done with some alias magic, which can only be done in the translation unit that defines the symbol.

Macros can make it prettier:

#define PRIVATE __attribute__((__visibility__("hidden")))
#define PUBLIC __attribute__((__visibility__("default")))
#define PRIVATE_ALIAS(Alias,OfWhat) \
    extern __typeof(OfWhat) Alias __attribute((__alias__(#OfWhat), \
                                 __visibility__("hidden")))

#if HERE
PUBLIC void test1(void) { }
PRIVATE_ALIAS(test1__,test1);
#else
PUBLIC void test1(void);
PRIVATE void test1__(void);
#endif

void call_test1(void) { test1(); }
void call_test1__(void) { test1__(); }

void call_ext0(void) { void ext0(void); ext0(); }
void call_ext1(void) { PRIVATE void ext1(void); ext1(); }

The above compiles (-O3, x86-64) into:

call_test1:
        jmp     test1@PLT
call_test1__:
        jmp     test1__
call_ext0:
        jmp     ext0@PLT
call_ext1:
        jmp     ext1

(Defining HERE=1 additionally inlines the test1 call since it's small and local and -O3 is on).

Live example at https://godbolt.org/g/eZvmp7.

Grogan answered 21/6, 2017 at 6:45 Comment(2)
"breaks the C language guarantee" - which one?Cathleencathlene
@Cathleencathlene Thanks for the comment. IDK why I had the last paragraph there. Just tried it and -fno-semantic-interposition doesn't seem to eliminate PLT indirection where an actual function call is made. I've deleted it for now. The believe the rest should be OK, esp. the part about having both a hidden and a visible alias for the same object.Grogan
C
3

If you can't change the source code, you could use a big-hammer: -Bsymbolic linker flag:

When creating a shared library, bind references to global symbols to the definition within the shared library, if any. Normally, it is possible for a program linked against a shared library to override the definition within the shared library. This option is only meaningful on ELF platforms which support shared libraries.

But beware that it will break if some parts of the library rely on symbol interposition. I'd recommend to go with hiding functions that don't need to be exported (by annotating them with __attribute__((visibility("hidden")))) or calling them through hidden aliases (specifically designed to do PLT-less intra-library calls in a controlled fashion).

Cathleencathlene answered 10/11, 2016 at 16:15 Comment(0)
G
3

If you declare test1() hidden (__attribute__((__visibility__("hidden"))), the jump will be direct.

Now test1() may not be defined in its source translation unit as hidden, but I believe no harm should come from that discrepancy except the C language guarantee that &test1 == &test1 might be broken for you at runtime if one of the pointers was obtained via a hidden reference and one via a public one (the public reference might have been interposed via preloading or a DSO that came before the current one in the lookup scope, while the hidden reference (which results in direct jumps) effective prevents any kind of interposition)

A more proper way to deal with this would be to define two names for test1()—a public name and a private/hidden name.

In gcc and clang, this can be done with some alias magic, which can only be done in the translation unit that defines the symbol.

Macros can make it prettier:

#define PRIVATE __attribute__((__visibility__("hidden")))
#define PUBLIC __attribute__((__visibility__("default")))
#define PRIVATE_ALIAS(Alias,OfWhat) \
    extern __typeof(OfWhat) Alias __attribute((__alias__(#OfWhat), \
                                 __visibility__("hidden")))

#if HERE
PUBLIC void test1(void) { }
PRIVATE_ALIAS(test1__,test1);
#else
PUBLIC void test1(void);
PRIVATE void test1__(void);
#endif

void call_test1(void) { test1(); }
void call_test1__(void) { test1__(); }

void call_ext0(void) { void ext0(void); ext0(); }
void call_ext1(void) { PRIVATE void ext1(void); ext1(); }

The above compiles (-O3, x86-64) into:

call_test1:
        jmp     test1@PLT
call_test1__:
        jmp     test1__
call_ext0:
        jmp     ext0@PLT
call_ext1:
        jmp     ext1

(Defining HERE=1 additionally inlines the test1 call since it's small and local and -O3 is on).

Live example at https://godbolt.org/g/eZvmp7.

Grogan answered 21/6, 2017 at 6:45 Comment(2)
"breaks the C language guarantee" - which one?Cathleencathlene
@Cathleencathlene Thanks for the comment. IDK why I had the last paragraph there. Just tried it and -fno-semantic-interposition doesn't seem to eliminate PLT indirection where an actual function call is made. I've deleted it for now. The believe the rest should be OK, esp. the part about having both a hidden and a visible alias for the same object.Grogan

© 2022 - 2024 — McMap. All rights reserved.