Run-time mocking in C? -
this has been pending long time in list now. in brief - need run mocked_dummy()
in place of dummy()
on run-time, without modifying factorial()
. not care on entry point of software. can add number of additional functions (but cannot modify code within /*---- not modify ----*/
).
why need this?
unit tests of legacy c modules. know there lot of tools available around, if run-time mocking possible can change ut approach (add reusable components) make life easier :).
platform / environment?
linux, arm, gcc.
approach i'm trying with?
- i know gdb uses trap/illegal instructions adding breakpoints (gdb internals).
- make code self modifiable.
- replace
dummy()
code segment illegal instruction, , return immediate next instruction. - control transfers trap handler.
- trap handler reusable function reads unix domain socket.
- address of
mocked_dummy()
function passed (read map file). - mock function executes.
there problems going ahead here. found approach tedious , requires amount of coding, in assembly too.
i found, under gcc each function call can hooked / instrumented, again not useful since the function intended mocked anyway executed.
is there other approach use?
#include <stdio.h> #include <stdlib.h> void mocked_dummy(void) { printf("__%s__()\n",__func__); } /*---- not modify ----*/ void dummy(void) { printf("__%s__()\n",__func__); } int factorial(int num) { int fact = 1; printf("__%s__()\n",__func__); while (num > 1) { fact *= num; num--; } dummy(); return fact; } /*---- not modify ----*/ int main(int argc, char * argv[]) { int (*fp)(int) = atoi(argv[1]); printf("fp = %x\n",fp); printf("factorial of 5 = %d\n",fp(5)); printf("factorial of 5 = %d\n",factorial(5)); return 1; }
this question i've been trying answer myself. have requirement want mocking method/tools done in same language application. unfortunately cannot done in c in portable way, i've resorted might call trampoline or detour. falls under "make code self modifiable." approach mentioned above. change bytes of function @ runtime jump our mock function.
#include <stdio.h> #include <stdlib.h> // additional headers #include <stdint.h> // uint32_t #include <sys/mman.h> // mprotect #include <errno.h> // errno void mocked_dummy(void) { printf("__%s__()\n",__func__); } /*---- not modify ----*/ void dummy(void) { printf("__%s__()\n",__func__); } int factorial(int num) { int fact = 1; printf("__%s__()\n",__func__); while (num > 1) { fact *= num; num--; } dummy(); return fact; } /*---- not modify ----*/ typedef void (*dummy_fun)(void); void set_run_mock() { dummy_fun run_ptr, mock_ptr; uint32_t off; unsigned char * ptr, * pg; run_ptr = dummy; mock_ptr = mocked_dummy; if (run_ptr > mock_ptr) { off = run_ptr - mock_ptr; off = -off - 5; } else { off = mock_ptr - run_ptr - 5; } ptr = (unsigned char *)run_ptr; pg = (unsigned char *)(ptr - ((size_t)ptr % 4096)); if (mprotect(pg, 5, prot_read | prot_write | prot_exec)) { perror("couldn't mprotect"); exit(errno); } ptr[0] = 0xe9; //x86 jmp rel32 ptr[1] = off & 0x000000ff; ptr[2] = (off & 0x0000ff00) >> 8; ptr[3] = (off & 0x00ff0000) >> 16; ptr[4] = (off & 0xff000000) >> 24; } int main(int argc, char * argv[]) { // run realz factorial(5); // set jmp set_run_mock(); // run mock dummy factorial(5); return 0; }
portability explanation...
mprotect() - changes memory page access permissions can write memory holds function code. isn't portable, , in winapi env, may need use virtualprotect() instead.
the memory parameter mprotect aligned previous 4k page, can change system system, 4k appropriate vanilla linux kernel.
the method use jmp mock function put down our own opcodes, biggest issue portability because opcode i've used work on little endian x86 (most desktops). need updated each arch plan run on (which semi-easy deal in cpp macros.)
the function has @ least 5 bytes. case because every function normally has @ least 5 bytes in prologue , epilogue.
potential improvements...
the set_mock_run() call setup accept parameters reuse. also, save 5 overwritten bytes original function restore later in code if desire.
i'm unable test, i've read in arm... you'd similar can jump address (not offset) branch opcode... unconditional branch you'd have first bytes 0xea , next 3 bytes address.
chenz
Comments
Post a Comment