寻找方法'模拟' POSIX在C/C++中功能代码

发布于 2025-01-22 09:01:55 字数 718 浏览 0 评论 0原文

我正在尝试找到一些优雅的方法来模拟和存根功能调用标准C库功能。 虽然仅通过链接测试中的其他C文件,但对项目的C文件的固定调用很容易,但标准C函数的固定很难。 它们在链接时就在那里。

当前,我的方法是包括我的test.cpp文件中的代码下测试,并放置这样的定义:

#include <stdio.h>
#include <gtest/gtest.h>
#include "mymocks.h"

CMockFile MockFile;
#define open MockFile.open
#define close MockFile.close
#define read MockFile.read
#include "CodeUnderTestClass.cpp"
#undef open
#undef close
#undef read

// test-class here

这很麻烦,有时我跨过代码,将“打开”用作其他位置的成员名称或导致其他碰撞和其他碰撞和问题。还有一些代码需要不同的定义和包括测试代码的情况。

那么还有其他选择吗?一些链接时间技巧或运行时技巧以覆盖标准C功能?我考虑过运行时挂接功能,但这可能会过度,因为通常将二进制代码加载仅读取。

我的单位测试仅在AMD64上与GCC一起在Debian-Linux上进行。因此,也欢迎GCC,X64或Linux特定技巧。

我知道,在使用C函数的抽象版本的所有代码中重写所有代码是一个选项,但提示对我来说不是很有用。

I am trying to find somewhat elegant ways to mock and stub function calls to the standard C library functions.
While stubbing-off calls to C files of the project is easy by just linking other C files in the tests, stubbing the standard C functions is harder.
They are just there when linking.

Currently, my approach is to include the code-under-test from my test.cpp file, and placing defines like this:

#include <stdio.h>
#include <gtest/gtest.h>
#include "mymocks.h"

CMockFile MockFile;
#define open MockFile.open
#define close MockFile.close
#define read MockFile.read
#include "CodeUnderTestClass.cpp"
#undef open
#undef close
#undef read

// test-class here

This is cumbersome, and sometimes I run across code that uses 'open' as member names elsewhere or causes other collisions and issues with it. There are also cases of the code needing different defines and includes than the test-code.

So are there alternatives? Some link-time tricks or runtime tricks to override standard C functions? I thought about run-time hooking the functions but that might go too far as usually binary code is loaded read-only.

My unit-tests run only on Debian-Linux with gcc on amd64. So gcc, x64 or Linux specific tricks are also welcome.

I know that rewriting all the code-under-test to use an abstracted version of the C functions is an option, but that hint is not very useful for me.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

揽清风入怀 2025-01-29 09:01:55

使用库预加载以自己的方式替换系统库。
考虑以下测试程序代码,mytest.c

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main(void) {
    char buf[256];
    int fd = open("file", O_RDONLY);
    if (fd >= 0) {
        printf("fd == %d\n", fd);
        int r = read(fd, buf, sizeof(buf));
        write(0, buf, r);
        close(fd);
    } else {
        printf("can't open file\n");
    }
    return 0;
}

它将从当前目录打开一个名为file的文件,打印其描述符号码(通常3 ),读取其内容,然后在标准输出(Deciptor 0)上打印它。

现在,这是您的测试库代码,mock.c

#include <string.h>
#include <unistd.h>

int open(const char *pathname, int flags) {
    return 100;
}

int close(int fd) {
    return 0;
}

ssize_t read(int fd, void *buf, size_t count) {
    strcpy(buf, "TEST!\n");
    return 7;
}

将其编译到共享库,称为mock.so

$ gcc -shared -fpic -o mock.so mock.c 

如果您编译了mytest.cmytest二进制文件中,使用以下命令运行:

$ LD_PRELOAD=./mock.so ./mytest

您应该看到输出:

fd == 100
TEST!

mock.c.c中定义的函数。 ,因此执行您的代码,而不是系统库中的代码。

更新:

如果要使用“原始”函数,则应使用dlopendlmap 和dlclose函数。因为我不想混乱上一个示例,所以这是新的示例,与以前的mock.c加上动态符号加载的内容相同:

#include <stdio.h>
#include <dlfcn.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <gnu/lib-names.h>

// this declares this function to run before main()
static void startup(void) __attribute__ ((constructor));
// this declares this function to run after main()
static void cleanup(void) __attribute__ ((destructor));

static void *sDlHandler = NULL;

ssize_t (*real_write)(int fd, const void *buf, size_t count) = NULL;

void startup(void) {
    char *vError;
    sDlHandler = dlopen(LIBC_SO, RTLD_LAZY);
    if (sDlHandler == NULL) {
        fprintf(stderr, "%s\n", dlerror());
        exit(EXIT_FAILURE);
    }
    real_write = (ssize_t (*)(int, const void *, size_t))dlsym(sDlHandler, "write");
    vError = dlerror();
    if (vError != NULL) {
        fprintf(stderr, "%s\n", vError);
        exit(EXIT_FAILURE);
    }

}

void cleanup(void) {
    dlclose(sDlHandler);
}


int open(const char *pathname, int flags) {
    return 100;
}

int close(int fd) {
    return 0;
}

ssize_t read(int fd, void *buf, size_t count) {
    strcpy(buf, "TEST!\n");
    return 7;
}

ssize_t write(int fd, const void *buf, size_t count) {
    if (fd == 0) {
        real_write(fd, "mock: ", 6);
    }
    real_write(fd, buf, count);
    return count;
}

用以下方式编译:

$ gcc -shared -fpic -o mock.so mock.c -ldl

注意-ldl在命令末尾。

因此:启动函数将在main之前运行(因此,您无需将任何初始化代码放在原始程序中),然后初始化real_write to to成为原始函数。 清理功能将在main之后运行,因此您也无需在main函数的末尾添加任何“清洁”代码。
除了新实现的Write函数外,所有其余的工作原理与上一个示例完全相同。对于几乎所有的描述符,它将用作原始内容,对于文件描述符0,它将在原始内容之前写入一些额外的数据。在这种情况下,该程序的输出将为:

$ LD_PRELOAD=./mock.so ./mytest
fd == 100
mock: TEST!

Use library preloading to substitute system libraries with your own.
Consider following test program code, mytest.c:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main(void) {
    char buf[256];
    int fd = open("file", O_RDONLY);
    if (fd >= 0) {
        printf("fd == %d\n", fd);
        int r = read(fd, buf, sizeof(buf));
        write(0, buf, r);
        close(fd);
    } else {
        printf("can't open file\n");
    }
    return 0;
}

It will open a file called file from the current directory, print it's descriptor number (usually 3), read its content and then print it on the standard output (descriptor 0).

Now here is your test library code, mock.c:

#include <string.h>
#include <unistd.h>

int open(const char *pathname, int flags) {
    return 100;
}

int close(int fd) {
    return 0;
}

ssize_t read(int fd, void *buf, size_t count) {
    strcpy(buf, "TEST!\n");
    return 7;
}

Compile it to a shared library called mock.so:

$ gcc -shared -fpic -o mock.so mock.c 

If you compiled mytest.c to the mytest binary, run it with following command:

$ LD_PRELOAD=./mock.so ./mytest

You should see the output:

fd == 100
TEST!

Functions defined in mock.c were preloaded and used as a first match during the dynamic linking process, hence executing your code, and not the code from the system libraries.

Update:

If you want to use "original" functions, you should extract them "by hand" from the proper shared library, using dlopen, dlmap and dlclose functions. Because I don't want to clutter previous example, here's the new one, the same as previous mock.c plus dynamic symbol loading stuff:

#include <stdio.h>
#include <dlfcn.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <gnu/lib-names.h>

// this declares this function to run before main()
static void startup(void) __attribute__ ((constructor));
// this declares this function to run after main()
static void cleanup(void) __attribute__ ((destructor));

static void *sDlHandler = NULL;

ssize_t (*real_write)(int fd, const void *buf, size_t count) = NULL;

void startup(void) {
    char *vError;
    sDlHandler = dlopen(LIBC_SO, RTLD_LAZY);
    if (sDlHandler == NULL) {
        fprintf(stderr, "%s\n", dlerror());
        exit(EXIT_FAILURE);
    }
    real_write = (ssize_t (*)(int, const void *, size_t))dlsym(sDlHandler, "write");
    vError = dlerror();
    if (vError != NULL) {
        fprintf(stderr, "%s\n", vError);
        exit(EXIT_FAILURE);
    }

}

void cleanup(void) {
    dlclose(sDlHandler);
}


int open(const char *pathname, int flags) {
    return 100;
}

int close(int fd) {
    return 0;
}

ssize_t read(int fd, void *buf, size_t count) {
    strcpy(buf, "TEST!\n");
    return 7;
}

ssize_t write(int fd, const void *buf, size_t count) {
    if (fd == 0) {
        real_write(fd, "mock: ", 6);
    }
    real_write(fd, buf, count);
    return count;
}

Compile it with:

$ gcc -shared -fpic -o mock.so mock.c -ldl

Note the -ldl at the end of the command.

So: startup function will run before main (so you don't need to put any initialization code in your original program) and initialize real_write to be the original write function. cleanup function will run after main, so you don't need to add any "cleaning" code at the end of main function either.
All the rest works exactly the same as in the previous example, with the exception of newly implemented write function. For almost all the descriptors it will work as the original, and for file descriptor 0 it will write some extra data before the original content. In that case the output of the program will be:

$ LD_PRELOAD=./mock.so ./mytest
fd == 100
mock: TEST!
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文