Search code examples
c++visual-studioobfuscationcompiler-optimization

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


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.


Solution

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