加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

运行时嘲笑C?

发布时间:2020-12-16 03:40:00 所属栏目:百科 来源:网络整理
导读:这在我的列表中已经有很长时间了.简而言之 – 我需要运行mocked_dummy()代替dummy()ON RUN-TIME,而不需要修改factorial().我不在乎软件的入门点.我可以添加任何数量的附加功能(但是不能修改/ *中的代码—-不要修改—- * /). 为什么我需要这个? 对一些传统C
这在我的列表中已经有很长时间了.简而言之 – 我需要运行mocked_dummy()代替dummy()ON RUN-TIME,而不需要修改factorial().我不在乎软件的入门点.我可以添加任何数量的附加功能(但是不能修改/ *中的代码—-不要修改—- * /).

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

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

接近我正在尝试吗?

>我知道GDB使用陷阱/非法指令来添加断点(gdb internals).
>使代码自我修改.
>使用非法指令替换dummy()代码段,并返回作为即时下一条指令.
>控制转移到陷阱处理程序.
>陷阱处理程序是从unix域套接字读取的可重用功能.
> mocked_dummy()函数的地址被传递(从map文件中读取).
> Mock功能执行.

这里有问题.我也发现这个方法是繁琐的,需要很多的编码,一些也是组装的.

我也发现,在gcc下,每个函数调用可以是hooked / instrumented,但是再次不是很有用,因为该函数的意图是要嘲笑将无论如何都被执行.

还有其他方法可以使用吗?

#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 = %xn",fp);
    printf("factorial of 5 is = %dn",fp(5));
    printf("factorial of 5 is = %dn",factorial(5));
    return 1;
}

解决方法

这是一个我一直在试图回答自己的问题.我也有要求我想使用与我的应用程序相同的语言完成嘲弄的方法/工具.不幸的是,这不能在C中以便携式的方式完成,所以我已经诉诸于你可能称之为蹦床或绕行.这属于“使代码自我修改”.你上面提到的方法这是我们在运行时更改函数的实际字节,以跳转到我们的模拟函数.
#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__);
    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() – 这会更改内存页访问权限,以便我们可以实际写入保存功能代码的内存.这不是很便携,在WINAPI环境中,您可能需要使用VirtualProtect().

mprotect的内存参数与前一个4k页面对齐,这也可以从系统更改为系统,4k适用于香草linux内核.

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

该函数本身必须至少为5个字节.通常情况是这样,因为每个函数的序言和结尾通常都至少有5个字节.

潜在改进…

set_mock_run()调用可以很容易地设置为接受参数以供重用.另外,您可以从原始函数保存五个覆盖的字节,以便稍后在代码中恢复.

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

Chenz

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读