Home > Mobile >  make compiler copy function's code inside a other function passed as argument
make compiler copy function's code inside a other function passed as argument

Time:06-26

My question is very specific, i want force compiler to take the code of a funtion and copy it inside a another one, like inline or __forceinline keywords can do, but i want pass the function i want to copy in the other funtion, as an argument. Here is a simple example.

using pFunc = void(*)();
void func_1() { /*some code*/ }
void func_2(pFunc function) { /*some code*/ } //after compile i want this funtion takes no argument and copy the func_1 inside this.
int main()
{
   func_2(func_1);
}

so with this example the compiler will pass the pointer of func_1 as argunent to func_2, as expected. I tried add inline keyword for func_1 and also tried to pass the argument of func_2 as reference, but compiler didn't copied the func_1 inside func_2. Any idea how can i do that?

I use the compiler of visual studio(msvc) with toolset 2017(v141). My project platform is x64.

CodePudding user response:

You can use a noinline template function to get the asm you want

So you want the compiler to do constant-propagation into a clone of void func_2(pFunc f){ f(); }? Like what GCC might do with __attribute__((noinline)) but not noclone?

For example,

using pFunc = void(*)();
int sink, sink2;
#ifdef _MSC_VER
#define NOINLINE _declspec(noinline)
#else
#define NOINLINE __attribute__((noinline)) // noclone and/or noipa
#endif

__attribute__((always_inline))  // without this, gcc chooses to clone .constprop.0 with just a jmp func_2
void func_1() { sink = 1; sink2 = 2; }
NOINLINE static void func_2(pFunc function) {  function(); }

int main()
{
   func_2(func_1);
}

produces, with GCC11.3 -O2 or higher, or -O1 -fipa-cp, on Godbolt. (Clang is similar):

# GCC11 -O3  with C   name demangling
func_1():
        mov     DWORD PTR sink[rip], 1
        mov     DWORD PTR sink2[rip], 2
        ret
func_2(void (*)()) [clone .constprop.0]:
        mov     DWORD PTR sink[rip], 1
        mov     DWORD PTR sink2[rip], 2
        ret
main:
      # note no arg passed, calling a special version of the function
      # specialized for function = func_1
        call    func_2(void (*)()) [clone .constprop.0]
        xor     eax, eax
        ret

Of course if we hadn't disabled inlining of func_2, main would just call func_1. Or inline that body of func_1 into main and not do any calls.


MSVC might not be willing to do that "optimization", instead preferring to just inline func_2 into main as call func_1.

If you want to force it to make clunky asm that duplicates func_1 unnecessarily, you could use a template to do the same thing as constprop, taking the function pointer as a template arg, so you can instantiate func_2<func1> as a stand-alone non-inline function if you really want. (Perhaps with _declspec(noinline)).

Your func_2 can accept func_1 as an unused argument if you want.

using pFunc = void(*)();
int sink, sink2;
#ifdef _MSC_VER
#define NOINLINE _declspec(noinline)
#define ALWAYS_INLINE  /* */
#else
#define NOINLINE __attribute__((noinline)) // not noclone or noipa, we *want* those to happen
#define ALWAYS_INLINE  __attribute__((always_inline))
#endif

//ALWAYS_INLINE // Seems not needed for this case, with the template version
void func_1() { sink = 1; sink2 = 2; }

template <pFunc f>
NOINLINE void func_2() {  f(); }

int main()
{
   func_2<func_1>();
}

Compiles as desired with MSVC -O2 (Godbolt), and GCC/clang

int sink DD   01H DUP (?)                     ; sink
int sink2 DD  01H DUP (?)               ; sink2

void func_2<&void func_1(void)>(void) PROC              ; func_2<&func_1>, COMDAT
        mov     DWORD PTR int sink, 1             ; sink
        mov     DWORD PTR int sink2, 2      ; sink2
        ret     0
void func_2<&void func_1(void)>(void) ENDP              ; func_2<&func_1>

void func_1(void) PROC                           ; func_1, COMDAT
        mov     DWORD PTR int sink, 1             ; sink
        mov     DWORD PTR int sink2, 2      ; sink2
        ret     0
void func_1(void) ENDP                           ; func_1

main    PROC                                            ; COMDAT
$LN4:
        sub     rsp, 40                             ; 00000028H
        call    void func_2<&void func_1(void)>(void)       ; func_2<&func_1>
        xor     eax, eax
        add     rsp, 40                             ; 00000028H
        ret     0
main    ENDP

Note the duplicated bodies of func_1 and func_2.

You should check (with a disassembler) that the linker doesn't do identical code folding and just attach the both symbol names to one block of machine code.


I don't think this looks like much of an obfuscation technique; IDK why having a 2nd copy of a function with identical machine code would be a problem to reverse engineer. I guess it would maybe create more overall work, and people wouldn't notice that two calls to different functions are actually doing the same thing.

I mostly answered as an exercise in making a compiler spit out the asm I wanted it to, whether or not that has value to anyone else.

Obviously it only works for compile-time-constant function pointers; commenters have been discussing self-modifying code and scripting languages. If you wanted this for non-const function pointer args to func_1, you're completely out of luck in a language like C that's designed for strictly ahead-of-time compilation.

  • Related