在C中运行时模拟?

这已经在我的列表中待了很长时间。 简而言之 – 我需要在运行mocked_dummy()在运行mocked_dummy()运行mocked_dummy() ,而不需要修改factorial() 。 我不关心软件的切入点。 我可以添加任意数量的附加function(但不能修改/*---- do not modify ----*/内的代码/*---- do not modify ----*/ )。

我为什么需要这个?
对一些传统C模块进行unit testing。 我知道有很多可用的工具,但如果可以运行时模拟我可以改变我的UT方法(添加可重用的组件)让我的生活更轻松:)。

平台/环境?
Linux,ARM,gcc。

我正在尝试的方法?

  • 我知道GDB使用陷阱/非法指令来添加断点( gdb内部 )。
  • 使代码可自行修改。
  • 用非法指令替换dummy()代码段,并作为紧接的下一条指令返回
  • 控制转移到陷阱处理程序。
  • 陷阱处理程序是一个可重用的函数,它从unix域套接字读取。
  • 传递mocked_dummy()函数的地址(从映射文件中读取)。
  • 模拟函数执行。

从这里开始存在问题。 我还发现这种方法很繁琐,需要大量的编码,有些也在组装中。

我还发现,在gcc下,每个函数调用都可以被挂钩/检测 ,但是再次不是很有用,因为该函数是用来模拟的,无论如何都会被执行。

我可以使用其他任何方法吗?

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

这是我一直试图回答的问题。 我还要求我希望使用与我的应用程序相同的语言来完成模拟方法/工具。 不幸的是,这不能用C语言以便携的方式完成,所以我采用了你可能称之为蹦床或绕行的东西。 这属于“使代码可自我修改”。 你上面提到的方法。 这是我们在运行时更改函数的实际字节以跳转到我们的模拟函数。

 #include  #include  // Additional headers #include  // for uint32_t #include  // for mprotect #include  // 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; } 

便携性解释……

mprotect() – 这会更改内存页面访问权限,以便我们可以实际写入包含function代码的内存。 这不是很便携,在WINAPI环境中,您可能需要使用VirtualProtect()。

mprotect的内存参数与之前的4k页面对齐,这也可以从系统更改为系统,4k适用于vanilla linux内核。

我们用来jmp到mock函数的方法是实际放下我们自己的操作码,这可能是可移植性的最大问题,因为我使用的操作码只适用于小端x86(大多数桌面)。 因此,需要针对您计划运行的每个拱门更新(这在CPP宏中可能很容易处理)。

函数本身必须至少为五个字节。 通常是这种情况,因为每个函数通常在其序言和结尾中至少有5个字节。

潜在的改进……

可以轻松设置set_mock_run()调用以接受参数以供重用。 此外,如果需要,可以保存原始函数中的五个覆盖字节,以便稍后在代码中恢复。

我无法测试,但我已经在ARM中读到了…你会做类似但你可以跳转到一个地址(不是一个偏移)与分支操作码…对于一个无条件分支你是第一个字节是0xEA,接下来的3个字节是地址。

Chenz

test-dept是一个相对较新的Cunit testing框架,允许您对函数进行运行时存根。 我发现它很容易使用 – 这是他们的文档中的一个例子:

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

虽然下载部分有点过时,但似乎相当积极地开发 – 作者修复了我非常及时的问题。 你可以通过以下方式获得最新版本(我一直在使用,没有问题):

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

该版本于2011年10月更新。

但是,由于使用汇编程序实现了存根,因此可能需要一些努力才能使其支持ARM。

我过去使用过的方法运作良好如下。

对于每个C模块,发布其他模块可以使用的“接口”。 这些接口是包含函数指针的结构。

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

在初始化期间,每个模块使用其实现函数初始化这些函数指针。

编写模块测试时,可以动态地将这些函数指针更改为其模拟实现,并在测试后恢复原始实现。

例:

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

您可以使用LD_PRELOAD替换每个函数。 您必须创建一个由LD_PRELOAD加载的共享库。 这是一个标准函数,用于将不支持SOCKS的程序转换为SOCKS感知程序 。 这是一个解释它的教程。