运行时嘲讽用C? [英] Run-time mocking in C?

查看:157
本文介绍了运行时嘲讽用C?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这已现等待在我的名单很长一段时间。在短暂的 - 我需要运行 mocked_dummy()中的位置假人() 打开运行时间,无需修改因子()。我不关心对软件的入口点。我可以加起来任意数量的附加功能(但不能修改在 code / * ----不要修改---- * / )。

为什么我需要这个?结果
做一些传统的C模块的单元测试。我知道有很多可用的工具左右,但如果运行时嘲讽可能我可以改变我的UT办法(添加可重用的组件),使我的生活更轻松。)

平台/环境?结果
Linux中,ARM,海湾合作委员会。

这是我,试图方法?


  • 我知道GDB使用陷阱/非法指令添加断点( GDB内部的)。

  • 请在code自行修改。

  • 替换假人() code段与非法指令,和收益作为直接下一个指令。

  • 控制转移到陷阱处理程序。

  • 陷阱处理程序是一个可重用的功能,从Unix域套接字读取。

  • mocked_dummy地址()函数传递(从地图文件中读取)。

  • 模拟功能执行。

有问题,从这里走在前面。我还发现,该方法是繁琐的,需要良好的数额编码,有的在组装了。

我还发现,在海湾合作委员会的每个函数调用可以的上钩/仪表,但同样不是非常有用,因为该功能旨在嘲笑无论如何都会得到执行。

是否有其他办法,我可以使用?

 的#include<&stdio.h中GT;
#包括LT&;&stdlib.h中GT;无效mocked_dummy(无效)
{
    的printf(__%S __()\\ n,__ func__);
}/ * ----不要修改---- * /
虚拟无效(无效)
{
    的printf(__%S __()\\ n,__ func__);
}INT因子(INT NUM)
{
    INT事实= 1;
    的printf(__%S __()\\ n,__ func__);
    而(NUM→1)
    {
        事实上* = NUM​​;
        num--;
    }
    假();
    返回事实;
}
/ * ----不要修改---- * /INT主(INT ARGC,CHAR *的argv [])
{
    INT(* FP)(INT)=的atoi(ARGV [1]);
    的printf(FP =%X \\ n,FP);
    的printf(5阶乘是=%d个\\ N,FP(5));
    的printf(5阶乘是=%d个\\ N,阶乘(5));
    返回1;
}


解决方案

这是我一直在试图回答自己一个问题。我也有,我想在相同的语言我的应用程序要完成的嘲讽方法/工具的要求。不幸的是这不能用C来完成一个可移植的方式,所以我已经使出了可以称之为蹦床或绕道而行。这符合了使code自我修改。接近你上面提到。这是我们改变的实际字节功能的运行跳转到我们的模拟功能。

 的#include<&stdio.h中GT;
#包括LT&;&stdlib.h中GT;//附加头
#包括LT&;&stdint.h GT; //为uint32_t的
#包括LT&; SYS / mman.h> //为则mprotect
#包括LT&;&errno.h中GT; //为错误号无效mocked_dummy(无效)
{
    的printf(__%S __()\\ n,__ func__);
}/ * ----不要修改---- * /
虚拟无效(无效)
{
    的printf(__%S __()\\ n,__ func__);
}INT因子(INT NUM)
{
    INT事实= 1;
    的printf(__%S __()\\ n,__ func__);
    而(NUM→1)
    {
        事实上* = NUM​​;
        num--;
    }
    假();
    返回事实;
}
/ * ----不要修改---- * /无效的typedef(* dummy_fun)(无效);无效set_run_mock()
{
    dummy_fun run_ptr,mock_ptr;
    uint32_t的关闭;
    无符号字符* PTR,* PG;    run_ptr =假;
    mock_ptr = mocked_dummy;    如果(run_ptr> mock_ptr){
        关闭= run_ptr - mock_ptr;
        关闭= -off - 5;
    }
    其他{
        关闭= mo​​ck_ptr - run_ptr - 5;
    }    PTR =(无符号字符*)run_ptr;    PG =(无符号字符*)(PTR - ((为size_t)PTR%4096));
    如果(则mprotect(PG,5,PROT_READ | PROT_WRITE | PROT_EXEC)){
        PERROR(无法MPROTECT);
        退出(错误);
    }    ptr的[0] = 0xE9; // 86 JMP rel32
    PTR [1] =关闭和放大器; 0x000000FF;
    ptr的[2] =(关闭&放大器; 0x0000FF00)GT;> 8;
    ptr的[3] =(关闭&放大器; 0x00FF0000)GT;> 16;
    PTR [4] =(关闭&放大器; 0xFF000000)GT;> 24;
}INT主(INT ARGC,CHAR *的argv [])
{
    //为Realz的运行
    阶乘(5);    //设置JMP
    set_run_mock();    //运行模拟假人
    阶乘(5);    返回0;
}

可移植性解释...

则mprotect() - 这改变内存页面的访问权限,使我们实际上可以写一个保存功能code内存。这是不是很便携,并在WINAPI ENV,你可能需要用VirtualProtect()代替。

有关mprotect的内存参数对准previous 4k的页面,这也可以从系统切换到系统,4K适合香草Linux内核。

这是我们使用JMP的模拟功能的方法是实际放下我们自己的运codeS,这可能是与便携性的最大问题,因为运算code我用仅会的工作一个小端的x86(大多数台式机)。因此,这将需要更新的每个拱你打算上运行(这可能是半轻松应对CPP宏)

函数本身必须是至少五个字节。在通常情况下,因为每一个功能的正常的在其序幕和尾声,至少5个字节。

潜在的改进...

该set_mock_run()调用可以很容易地安装接受重复使用的参数。此外,您可以从保存的原始功能的五个覆盖字节,如果你渴望在code以后恢复。

我无法测试,但我读过,在ARM ...你会做类似的,但您可以跳转到一个地址(而不是偏移)与分支运算code ...这对于无条件转移你得第一字节是0xEA和下3个字节的地址。

Chenz

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;
}

解决方案

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

这篇关于运行时嘲讽用C?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆