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(×tamp);
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.
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.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.