Search code examples
c++mingwmingw-w64

Overriding a system function by using -wrap in mingw


For testautomation purposes, we want to override the time function, which is scattered across the code.

For this purpose, we are using the mingw (gcc) linker flag -Wl,--wrap=time, which allowed us to redirect calls to the system time function to a custom module, where we manipulated the time to match the acceleration of the automation.

For Windows, we also had to wrap around _time32, because mingw performs an inline forwarding from time to (the windows function) _time32.

This worked just fine, as long as we used the Mingw version 7.3.0 (32 bit). But after switching to Mingw 11.2.0 (64 bits) it just...stopped working.

We already tried wrapping around _time64 instead of _time32, but the wrap just doesn't happen.

When forward declaring the 'time' function manually, (extern "C" time_t time(time_t *);) the wrap works flawlessly, but the entire purpose of using wrap is to avoid having to change the entire source code.

I can see how/why it fails to wrap around time (the function being static __inline within the header and therefor 'defined' whenever the header is included), but _CRTIMP __time64_t __cdecl _time64(__time64_t *_Time); should be plenty 'undefined reference' for wrap to work.

Any ideas on how I could proceed in this matter?

The linker script I am currently working with:

TARGET = Sandbox
TEMPLATE = app
CONFIG  *= console
CONFIG  -= qt app_bundle

SOURCES += main.cpp \ #MainWindow.cpp
    SecondFile.c

QMAKE_CFLAGS        += -fsigned-char
QMAKE_CXXFLAGS      += -fsigned-char
QMAKE_LFLAGS        += -Wl,--wrap=__time64 -Wl,--wrap=time

main.cpp:

#include <stdio.h>
#include <time.h>

extern "C" {
    //time_t time(time_t *);
}

int main(int, char **)
{
    time_t timestamp;
    time(&timestamp);
    printf("%lu\n", timestamp);
    return 0;
}

SecondFile.c

#include <stdio.h>

time_t __wrap_time(time_t *p)
{
    if (p)
        *p = 1;
    return 1;
}

time_t __wrap__time32(time_t *p)
{
    return __wrap_time(p);
}

time_t __wrap__time64(time_t *p)
{
    return __wrap_time(p);
}

I'd like to note, that time_t is an architecture dependent typedef, so time_t is __time64_t in the context of this test.

Edit: After doing a little more digging, and running nm on the .o file, I got this output:

0000000000000000 b .bss
0000000000000000 d .data
0000000000000000 N .debug_abbrev
0000000000000000 N .debug_aranges
0000000000000000 N .debug_frame
0000000000000000 N .debug_frame$_Z6printfPKcz
0000000000000000 N .debug_info
0000000000000000 N .debug_line
0000000000000000 N .debug_rnglists
0000000000000000 p .pdata
0000000000000000 p .pdata$_Z6printfPKcz
0000000000000000 r .rdata
0000000000000000 r .rdata$zzz
0000000000000000 t .text
0000000000000000 t .text$_Z6printfPKcz
0000000000000000 r .xdata
0000000000000000 r .xdata$_Z6printfPKcz
                 U __imp___acrt_iob_func
                 U __imp__time64
                 U __main
                 U __mingw_vfprintf
0000000000000000 T _Z6printfPKcz
000000000000001f T main
0000000000000000 t time

This suggests, that whilst the preprocessor does not touch the call (I checked), the linker translates it to something entirely different.

However when trying to wrap the __imp__ function instead, I get a segmentation fault when trying to run the code.


Solution

  • It's a bit of a hack, but the issue can be solved by 'hiding' the call to time using a wrapper header file.

    By creating a proxy time.h header file (which should lodged in the include path, so #include <time.h> finds it), I can 'inject' my own calls.

    The header file could then look like:

    #include_next <time.h>
    
    extern time_t __wrap_time(time_t *);
    #define time(x) __wrap_time(x)
    

    What this does should be rather obvious:

    • #include_next <time.h> includes the real time.h, thereby giving you access to all the functions you aren't trying to mock.
    • The forward declaration (possibly needs to be wrapped with 'extern "C"') declares the function you want to wrap.
    • The macro replaces ALL calls to the function to the wrapped function (which is why you need the forward declaration).

    Just adding time(x)=__wrap_time(x) to the compile flags does not work, because it would also replace the declaration inside time.h, putting you back to square one. (And you'd still need the forward declaration)

    Including <ctime> also won't work, because within, #undef time is called, undoing the macro replacement you are injecting.

    This approach essentially circumvents the wrap flag altogether. In order to still call the original time (in order to call __real_time), you only need to #undef the macro and thereby access the original function.

    I hope this will help someone else.