qemu单元测试二

本文详细介绍了如何使用QEMU单元测试框架Glib进行测试,包括初始化测试环境、创建并添加测试用例、执行测试以及退出测试等步骤。以ARM下的tmp105测试用例为例,展示了如何实现测试用例的执行流程,包括创建测试环境、执行写入操作等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

qemu单元测试glib的测试框架,其使用可以参考:https://developer.gnome.org/glib/2.37/glib-Testing.html

对于一个用户来说使用这个框架比较简单,下面是一个arm下的tmp105的测试用例:

int main(int argc, char **argv)
{
    QTestState *s = NULL;
    int ret;


    g_test_init(&argc, &argv, NULL);


    s = qtest_start("-display none -machine n800");
    i2c = omap_i2c_create(OMAP2_I2C_1_BASE);
    addr = N8X0_ADDR;


    qtest_add_func("/tmp105/tx-rx", send_and_receive);


    ret = g_test_run();


    if (s) {
        qtest_quit(s);
    }
    g_free(i2c);


    return ret;
}

主要步骤是: 调用g_test_init初始化测试框架, 调用qtest_start启动测试的单板, 创建测试用例并添加到测试框架中,执行测试用例, 退出测试等。

这里面主要的函数是qtest _start,它主要功能是创建qemu的测试环境。这里qemu的执行和测试用例是分开的,两个进程通过socket进行通信。测试进程是客户端,qemu进程是服务器端。在tests/libtest.c:

QTestState *qtest_init(const char *extra_args)
{
    QTestState *s;
    int sock, qmpsock, i;
    gchar *pid_file;
    gchar *command;
    const char *qemu_binary;
    pid_t pid;


    qemu_binary = getenv("QTEST_QEMU_BINARY");
    g_assert(qemu_binary != NULL);


    s = g_malloc(sizeof(*s));


    s->socket_path = g_strdup_printf("/tmp/qtest-%d.sock", getpid());
    s->qmp_socket_path = g_strdup_printf("/tmp/qtest-%d.qmp", getpid());
    pid_file = g_strdup_printf("/tmp/qtest-%d.pid", getpid());


    sock = init_socket(s->socket_path);
    qmpsock = init_socket(s->qmp_socket_path);


    pid = fork();
    if (pid == 0) {
        command = g_strdup_printf("%s "
                                  "-qtest unix:%s,nowait "
                                  "-qtest-log /dev/null "
                                  "-qmp unix:%s,nowait "
                                  "-pidfile %s "
                                  "-machine accel=qtest "
                                  "%s", qemu_binary, s->socket_path,
                                  s->qmp_socket_path, pid_file,
                                  extra_args ?: "");
        execlp("/bin/sh", "sh", "-c", command, NULL);
        exit(1);
    }

    s->fd = socket_accept(sock);
    s->qmp_fd = socket_accept(qmpsock);

    s->rx = g_string_new("");
    s->pid_file = pid_file;
    s->child_pid = pid;
    for (i = 0; i < MAX_IRQ; i++) {
        s->irq_level[i] = false;
    }

    /* Read the QMP greeting and then do the handshake */
    qtest_qmp(s, "");
    qtest_qmp(s, "{ 'execute': 'qmp_capabilities' }");

    if (getenv("QTEST_STOP")) {
        kill(qtest_qemu_pid(s), SIGSTOP);
    }

    return s;
}
这个函数创建了qemu的执行进程,在qemu单元测试的情况下使用的的accel是qtest方式,因为在这个模式下quest并不需要执行指令,但是需要创建整个单板以方便测试。
以一个最简单的writew为例,执行流程如下: 首先测试单元执行这个指令:

static void omap_i2c_set_slave_addr(OMAPI2C *s, uint8_t addr)
{
    uint16_t data = addr;


    writew(s->addr + OMAP_I2C_SA, data);
    data = readw(s->addr + OMAP_I2C_SA);
    g_assert_cmphex(data, ==, addr);
}

writew最终会调用qtest_writew:

void qtest_writew(QTestState *s, uint64_t addr, uint16_t value)
{
    qtest_write(s, "writew", addr, value);
}
static void GCC_FMT_ATTR(2, 3) qtest_sendf(QTestState *s, const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    socket_sendf(s->fd, fmt, ap);
    va_end(ap);
}
qtest_sendf通过socket.c将cmd以及相关的参数传送给qemu进程

    } else if (strcmp(words[0], "writeb") == 0 ||
               strcmp(words[0], "writew") == 0 ||
               strcmp(words[0], "writel") == 0 ||
               strcmp(words[0], "writeq") == 0) {
        uint64_t addr;
        uint64_t value;

        g_assert(words[1] && words[2]);
        addr = strtoull(words[1], NULL, 0);
        value = strtoull(words[2], NULL, 0);

        if (words[0][5] == 'b') {
            uint8_t data = value;
            cpu_physical_memory_write(addr, &data, 1);
        } else if (words[0][5] == 'w') {
            uint16_t data = value;
            tswap16s(&data);
            cpu_physical_memory_write(addr, &data, 2);
        } else if (words[0][5] == 'l') {
            uint32_t data = value;
            tswap32s(&data);
            cpu_physical_memory_write(addr, &data, 4);
        } else if (words[0][5] == 'q') {
            uint64_t data = value;
            tswap64s(&data);
            cpu_physical_memory_write(addr, &data, 8);
        }
        qtest_send_prefix(chr);
        qtest_send(chr, "OK\n");

对write的处理是解析出addr,data,len的参数,然后调用cpu_phsical_memory_write完成这个操作

int qtest_init(void)
{
    CharDriverState *chr;


    g_assert(qtest_chrdev != NULL);
    
    configure_icount("0");
    chr = qemu_chr_new("qtest", qtest_chrdev, NULL);
    
    qemu_chr_add_handlers(chr, qtest_can_read, qtest_read, qtest_event, chr);
    qemu_chr_fe_set_echo(chr, true);
    
    inbuf = g_string_new("");
        
    if (qtest_log) {
        if (strcmp(qtest_log, "none") != 0) {
            qtest_log_fp = fopen(qtest_log, "w+");
        }
    } else {
        qtest_log_fp = stderr;
    }


    qtest_chr = chr;


    return 0;
}

在测试模式下qemu不会执行任何指令, 只会进行简单的处理。在cpus.c里面qemu初始化的时候选择qemu_dummy_cpu_thread_fn

static void *qemu_dummy_cpu_thread_fn(void *arg)
{
#ifdef _WIN32
    fprintf(stderr, "qtest is not supported under Windows\n");
    exit(1);
#else
    CPUState *cpu = arg; 
    sigset_t waitset;
    int r;

    qemu_mutex_lock_iothread();
    qemu_thread_get_self(cpu->thread);
    cpu->thread_id = qemu_get_thread_id();

    sigemptyset(&waitset);
    sigaddset(&waitset, SIG_IPI);

    /* signal CPU creation */
    cpu->created = true;
    qemu_cond_signal(&qemu_cpu_cond);

    cpu_single_env = cpu->env_ptr;
    while (1) {
        cpu_single_env = NULL;
        qemu_mutex_unlock_iothread();
        do { 
            int sig; 
            r = sigwait(&waitset, &sig);
        } while (r == -1 && (errno == EAGAIN || errno == EINTR));
        if (r == -1) {
            perror("sigwait");
            exit(1);
        }    
        qemu_mutex_lock_iothread();
        cpu_single_env = cpu->env_ptr;
        qemu_wait_io_event_common(cpu);
    }




                
### QEMU 启动测试器的使用指南 QEMU 提供了一种强大的虚拟化环境,可以模拟各种硬件平台并运行相应的操作系统或应用程序。为了启动和管理测试器,通常需要结合特定的目标架构以及调试工具来完成。 #### 1. 安装 QEMU 在 macOS 上可以通过图形界面工具 **QEMU Manager** 来简化安装过程[^1]。对于其他操作系统,则可以直接通过包管理器或者编译源码的方式安装 QEMU。例如,在 Linux 系统中: ```bash sudo apt update && sudo apt install qemu-system-arm ``` 如果目标是针对 ARM 架构进行开发,推荐使用由 Liviu Ionescu 维护的 **QEMU Arm xPack** 版本[^2]。该版本提供了更便捷的安装方式,并且内置了 Semihosting 和 ITM 支持,适合嵌入式开发场景。 --- #### 2. 配置 QEMU 测试环境 要启动一个测试程序,首先需准备对应的镜像文件(如根文件系统、内核映像等)。以下是基本命令模板: ```bash qemu-system-arm \ -machine versatilepb \ -cpu cortex-a9 \ -kernel /path/to/zImage \ -initrd /path/to/initramfs.cpio.gz \ -append "root=/dev/ram rdinit=/sbin/init" \ -nographic ``` - `-machine` 参数指定仿真机型。 - `-cpu` 设置 CPU 类型。 - `-kernel` 加载内核映像。 - `-initrd` 指定初始 RAM 文件系统。 - `-append` 添加引导参数。 - `-nographic` 关闭图形输出,仅保留串口日志。 当测试的是用户空间应用而非整个操作系统时,可直接加载 ELF 可执行文件作为入口点。例如: ```bash qemu-arm ./test & ps -ef | grep -v grep | grep "test" ``` 此脚本会将 `./test` 应用置于后台运行,并确认其进程是否存在[^3]。 --- #### 3. 调试与监控 为了让开发者更好地分析程序行为,建议开启以下功能: - **启用调试模式**:重新编译 QEMU 并加入 `--enable-debug` 标记[^4]。 - **记录日志信息**:利用 `-d` 参数收集未实现指令或其他异常事件的数据流。例如: ```bash qemu-system-arm ... -d unimp,guest,exec -D debug.log ``` 这将在当前目录下生成名为 `debug.log` 的跟踪文件。 另外还可以借助 GDB 工具远程连接到正在运行中的实例来进行单步调试操作: ```bash qemu-system-arm ... -s -S & gdb-multiarch target remote :1234 ``` 其中 `-s` 表示监听端口号,默认为 1234;而 `-S` 则暂停执行直到收到继续信号为止。 --- #### 4. 单元测试框架集成 对于希望自动化验证代码质量的情况来说,构建一套完善的 CI/CD 管道显得尤为重要。Xpack 中已经包含了部分预定义好的 Makefile 规则用于驱动此类任务——只需简单调用即可触发相关流程: ```makefile make check make check-qtest-arm ``` 这些命令能够覆盖大部分常见边界条件下的表现评估需求[^2]^。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值