Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Run-time mocking in C?

Tags:

c

mocking

gdb

arm

This has been pending for a long time in my list now. In brief - I need to run mocked_dummy() in the place of dummy() ON RUN-TIME, without modifying factorial(). I do not care on the entry point of the software. I can add up any number of additional functions (but cannot modify code within /*---- do not modify ----*/).

Why do I need this?
To do unit tests of some legacy C modules. I know there are a lot of tools available around, but if run-time mocking is possible I can change my UT approach (add reusable components) make my life easier :).

Platform / Environment?
Linux, ARM, gcc.

Approach that I'm trying with?

  • I know GDB uses trap/illegal instructions for adding up breakpoints (gdb internals).
  • Make the code self modifiable.
  • Replace dummy() code segment with illegal instruction, and return as immediate next instruction.
  • Control transfers to trap handler.
  • Trap handler is a reusable function that reads from a unix domain socket.
  • Address of mocked_dummy() function is passed (read from map file).
  • Mock function executes.

There are problems going ahead from here. I also found the approach is tedious and requires good amount of coding, some in assembly too.

I also found, under gcc each function call can be hooked / instrumented, but again not very useful since the the function is intended to be mocked will anyway get executed.

Is there any other approach that I could use?

#include <stdio.h>
#include <stdlib.h>

void mocked_dummy(void)
{
    printf("__%s__()\n",__func__);
}

/*---- do 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;
}
/*---- do not modify ----*/

int main(int argc, char * argv[])
{
    int (*fp)(int) = atoi(argv[1]);
    printf("fp = %x\n",fp);
    printf("factorial of 5 is = %d\n",fp(5));
    printf("factorial of 5 is = %d\n",factorial(5));
    return 1;
}
like image 246
Kamath Avatar asked Mar 08 '12 06:03

Kamath


4 Answers

test-dept is a relatively recent C unit testing framework that allows you to do runtime stubbing of functions. I found it very easy to use - here's an example from their docs:

void test_stringify_cannot_malloc_returns_sane_result() {
  replace_function(&malloc, &always_failing_malloc);
  char *h = stringify('h');
  assert_string_equals("cannot_stringify", h);
}

Although the downloads section is a little out of date, it seems fairly actively developed - the author fixed an issue I had very promptly. You can get the latest version (which I've been using without issues) with:

svn checkout http://test-dept.googlecode.com/svn/trunk/ test-dept-read-only

the version there was last updated in Oct 2011.

However, since the stubbing is achieved using assembler, it may need some effort to get it to support ARM.

like image 86
Timothy Jones Avatar answered Sep 28 '22 02:09

Timothy Jones


This is a question I've been trying to answer myself. I also have the requirement that I want the mocking method/tools to be done in the same language as my application. Unfortunately this cannot be done in C in a portable way, so I've resorted to what you might call a trampoline or detour. This falls under the "Make the code self modifiable." approach you mentioned above. This is were we change the actually bytes of a function at runtime to jump to our mock function.

#include <stdio.h>
#include <stdlib.h>

// Additional headers
#include <stdint.h> // for uint32_t
#include <sys/mman.h> // for mprotect
#include <errno.h> // for errno

void mocked_dummy(void)
{
    printf("__%s__()\n",__func__);
}

/*---- do 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;
}
/*---- do 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 for realz
    factorial(5);

    // Set jmp
    set_run_mock();

    // Run the mock dummy
    factorial(5);

    return 0;
}

Portability explanation...

mprotect() - This changes the memory page access permissions so that we can actually write to memory that holds the function code. This isn't very portable, and in a WINAPI env, you may need to use VirtualProtect() instead.

The memory parameter for mprotect is aligned to the previous 4k page, this also can change from system to system, 4k is appropriate for vanilla linux kernel.

The method that we use to jmp to the mock function is to actually put down our own opcodes, this is probably the biggest issue with portability because the opcode I've used will only work on a little endian x86 (most desktops). So this would need to be updated for each arch you plan to run on (which could be semi-easy to deal with in CPP macros.)

The function itself has to be at least five bytes. The is usually the case because every function normally has at least 5 bytes in its prologue and epilogue.

Potential Improvements...

The set_mock_run() call could easily be setup to accept parameters for reuse. Also, you could save the five overwritten bytes from the original function to restore later in the code if you desire.

I'm unable to test, but I've read that in ARM... you'd do similar but you can jump to an address (not an offset) with the branch opcode... which for an unconditional branch you'd have the first bytes be 0xEA and the next 3 bytes are the address.

Chenz

like image 44
Crazy Chenz Avatar answered Sep 28 '22 02:09

Crazy Chenz


An approach that I have used in the past that has worked well is the following.

For each C module, publish an 'interface' that other modules can use. These interfaces are structs that contain function pointers.

struct Module1 
{
    int (*getTemperature)(void);
    int (*setKp)(int Kp);
}

During initialization, each module initializes these function pointers with its implementation functions.

When you write the module tests, you can dynamically changes these function pointers to its mock implementations and after testing, restore the original implementation.

Example:

void mocked_dummy(void)
{
    printf("__%s__()\n",__func__);
}
/*---- do not modify ----*/
void dummyFn(void)
{
    printf("__%s__()\n",__func__);
}
static void (*dummy)(void) = dummyFn;
int factorial(int num)
{
    int                      fact = 1;
        printf("__%s__()\n",__func__);
    while (num > 1)
    {
        fact *= num;
        num--;
    }
    dummy();
    return fact;
}

/*---- do not modify ----*/
int main(int argc, char * argv[])
{
    void (*oldDummy) = dummy;

/* with the original dummy function */
    printf("factorial of 5 is = %d\n",factorial(5));

/* with the mocked dummy */
    oldDummy = dummy;   /* save the old dummy */
    dummy = mocked_dummy; /* put in the mocked dummy */
    printf("factorial of 5 is = %d\n",factorial(5));
    dummy = oldDummy; /* restore the old dummy */
    return 1;
}
like image 26
shr Avatar answered Sep 28 '22 02:09

shr


You can replace every function by the use of LD_PRELOAD. You have to create a shared library, which gets loaded by LD_PRELOAD. This is a standard function used to turn programs without support for SOCKS into SOCKS aware programs. Here is a tutorial which explains it.

like image 35
ceving Avatar answered Sep 28 '22 04:09

ceving