Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Building a Socket-Based DiffTest Environment Between NEMU and QEMU

Tech Oct 19 1

Overview

This guide demonstrates how to establish a differential testing environment using NEMU as the device under test (DUT) and QEMU as the reference implementation (REF). The setup employs socket communication for synchronization and comparison.

NEMU Compilation Setup

Configuration

Execute the following commands to install dependencies and configure NEMU:

apt install build-essential man gcc gdb git libreadline-dev libsdl2-dev zstd libzstd-dev
git clone https://github.com/OpenXiangShan/NEMU.git
cd NEMU/
export NEMU_HOME=/path/to/NEMU
make riscv64-benos_defconfig
make menuconfig

Configure the following settings:

  • Build Options → Optimization Level: Set to O0
  • Build Options → Enable link-time optimization: Disabled
  • Build Options → Enable debug information: Enabled
  • Testing and Debugging → Enable differential testing: Enabled with socket communication
  • Testing and Debugging → Enable debug features: Instruction tracing and watchpoint enabled
  • Testing and Debugging → Enable rich Log: Instruction tracing enabled

Build Configuration Modifications

Remove all -Werror flags from NEMU/scripts/build.mk:

CFLAGS  := -O2 -MMD -Wall $(INCLUDES) $(CFLAGS)
CXXFLAGS  := -O2 -MMD -Wall --std=c++17 $(XINCLUDES) $(CFLAGS)

Register Check Function Modification

Update isa_difftest_checkregs in NEMU/src/isa/riscv64/difftest/dut.c to check only gpr[32] and PC registers:

bool isa_difftest_checkregs(CPU_state *ref_r, vaddr_t pc) {
  csr_prepare();
#ifdef CONFIG_DIFFTEST_REF_SPIKE
  cpu.mip &= 0xffffff4f;
#endif
  if(cpu.mip != ref_r->mip) ref_r->mip = cpu.mip;
  if (memcmp(&cpu.gpr[1], &ref_r->gpr[1], ARRLEN(cpu.gpr) - sizeof(cpu.gpr[0])) || (cpu.pc != ref_r->pc)) {
    int i;
    for (i = 1; i < ARRLEN(cpu.gpr); i ++) {
      difftest_check_reg(reg_name(i, 4), pc, ref_r->gpr[i]._64, cpu.gpr[i]._64);
    }
    difftest_check_reg("pc", pc, ref_r->pc, cpu.pc);
    return false;
  }
#ifdef CONFIG_DIFFTEST_STORE_COMMIT
  return difftest_check_store(pc);
#else
  return true;
#endif
}

PMP Permission Check Update

Modify isa_pmp_check_permission in NEMU/src/isa/riscv64/system/mmu.c:

bool isa_pmp_check_permission(paddr_t addr, int len, int type, int out_mode) {
  return true;
}

Compilation

make -j`nproc`

The compiled executable riscv64-nemu-interpreter will be generated in NEMU/build/.

QEMU Socket Diff Compilation

ISA Configuraton

Set ISA to riscv64 in NEMU/scripts/isa.mk:

ISA ?= riscv64

Build Flags Modification

Add -ldl to LDFLAGS in NEMU/scripts/build.mk and update optimization flags:

CFLAGS  := -O0 -g -MMD -Wall $(INCLUDES) $(CFLAGS)
CXXFLAGS  := -O0 -g -MMD -Wall --std=c++17 $(XINCLUDES) $(CFLAGS)
LDFLAGS := -O0 -g $(LDFLAGS) -ldl

Header File Adjustments

Add #include <difftest.h> to NEMU/tools/qemu-socket-diff/include/common.h and remove it from NEMU/tools/qemu-socket-diff/src/diff-test.c.</difftest.h>

Register Structure Update

Remove fpr[32] from union isa_gdb_regs in NEMU/tools/qemu-socket-diff/include/isa/riscv64.h:

union isa_gdb_regs {
  struct {
    uint64_t gpr[32];
    uint64_t pc;
  };
  struct {
    uint32_t array[DIFFTEST_REG_SIZE/sizeof(uint32_t)];
  };
};

Register Retrieval Function

Update gdb_getregs in NEMU/tools/qemu-socket-diff/src/gdb-host.c:

bool gdb_getregs(union isa_gdb_regs *r) { 
  gdb_send(conn, (const uint8_t *)"g", 1);
  size_t size;
  uint8_t *reply = gdb_recv(conn, &size);

  int i;
  uint8_t *p = reply;
  uint8_t c;
  assert(size % (sizeof(uint64_t) * 2) == 0);
  assert((size / 2) <= sizeof(union isa_gdb_regs));
  for (i = 0; i < size / (sizeof(uint64_t) * 2); i ++) {
    c = p[16];
    p[16] = '\0';
    ((uint64_t*)(r->array))[i] = gdb_decode_hex_str(p);
    p[16] = c;
    p += 16;
  }
 
  free(reply);
  return true; 
}

Register Copy Function

Modify difftest_regcpy in NEMU/tools/qemu-socket-diff/src/diff-test.c:

void difftest_regcpy(void *dut, bool direction) {
  union isa_gdb_regs qemu_r;
  gdb_getregs(&qemu_r);
  int dut_pc_off = 32 * sizeof(uint64_t) * 2 + 18 * sizeof(uint64_t);
  if (direction == DIFFTEST_TO_REF) {
    memcpy(qemu_r.gpr, dut, (32 * sizeof(uint64_t)));
    memcpy(&(qemu_r.pc), (dut + dut_pc_off), sizeof(uint64_t));
    gdb_setregs(&qemu_r);
  } else {
    memcpy(dut, qemu_r.gpr, (32 * sizeof(uint64_t)));
    memcpy((dut + dut_pc_off), &(qemu_r.pc), sizeof(uint64_t));
  }
}

QEMU Instruction Logging

Update execlp call in difftest_init to enable instruction logging:

execlp(ISA_QEMU_BIN, ISA_QEMU_BIN, ISA_QEMU_ARGS "-S", "-gdb", buf, "-nographic", "-d", "in_asm", "-D", "./qemu.log", NULL);

Compilation

cd tools/qemu-socket-diff/
make -j`nproc`

The riscv64-qemu-so shared library will be generated in NEMU/tools/qemu-socket-diff/build.

Running DiffTest

Copy the following files to NEMU/build directory:

  • riscv64-qemu-so
  • benos_payload.bin
  • qemu-system-riscv64
  • opensbi-riscv64-generic-fw_dynamic.bin

Execute NEMU with DiffTest enabled:

./riscv64-nemu-interpreter -b benos_payload.bin -d ./riscv64-qemu-so -l nemu.log

NEMU will fork a process to launch QEMU and begin instruction-by-instruction comparison. Any discrepancies will be logged to nemu.log.

If encountering port conflicts, terminate existing QEMU processes:

kill $(ps aux | grep qemu | grep -v grep | awk '{print $2}')

Successful Execution

When both implementations produce identical results, BenOS will output "Welcome RISC-V!" via serial port, and nemu.log will confirm instruction matching.

Detection of Differences

When discrepancies are detected, execution halts and register differences are reported in nemu.log. For example:

pc is different after executing instruction at pc = 0x00000000800000a8, right = 0x0000000000000000, wrong = 0x0000000080200000

This indicates a mismatch in PC register values after mret instruction execution.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.