difuzz框架详解——从初学者角度理解处理器模糊测试(2)

0. 前言

  • 本篇博客主要的内容都是在阅读difuzz框架,由于目前docker用不了,我用的是processorfuzz的docker镜像进行阅读讲解。
  • 本博客最核心关注的点在于:
    • 如何生成risc-v处理器,如rocket-chip,boom的处理器指令?
    • 如何利用cocotb把生成的处理器指令放入这些设计中进行仿真?

1. 框架设置

  • 打开github,搜索ProcessorFuzz,下载对应的docker。
  • docker镜像拉取的方法用:docker load < processorfuzz_docker_img.tar。
  • 根据自己的需要设置开发路径和方法。

2. 框架阅读

2.1. 处理器指令是怎么生成的?

difuzz主要是通过类rvMutator进行指令生成,生成的指令类型是RV64G:self.inst_generator = rvInstGenerator('RV64G')

处理器指令分成三个部分,分别是PREFIX,MAIN和SUFFIX。PREFIX和SUFFIX的数量分别是3和5:

self.num_prefix = 3
self.num_words = 100
self.num_suffix = 5

2.1.q1 问题:为什么用的是RV64G?

  • 通用性与完整性:RV64G 是一个通用的、功能完整的 RISC-V 指令集,包含了整数运算、浮点运算、原子操作等指令,适合广泛的应用场景。

2.1.q2. 问题:哪里看Rocket使用的是RV64G的指令集

2.1.1. PREFIX指令

这里的words指的是一个系列处理器指令的集合;因为如PREFIX的指令,它会随机从rv_zicsr中选择一个opcode:

def get_word(self, part):
 if part == PREFIX:
     opcode = random.choice(list(rv_zicsr.keys()))
     label_num = self.prefix_num
     self.prefix_num += 1

rv_zicsr中选择一个后opcode后,这个opcode需要在opcodes_words里面匹配和其对应的指令,如csrrw(属于rv_zicsr),它在csr_r指令中

# csr_r: CSR Read/Write Instructions
    # - 处理 CSR 读/写指令,如 'csrrw', 'csrrs', 'csrrc'
    # - 指令格式: 'csrrw rd, csr, rs1'
    # - 操作数:
    #   - rd: 目标寄存器,存储读取的 CSR 值
    #   - csr: CSR 寄存器地址
    #   - rs1: 源寄存器,提供要写入 CSR 的新值
    'csr_r': (['csrrw', 'csrrs', 'csrrc'], word_csr_r),

他则需要按照方法word_csr_r处理周围的指令,包括在这个word里面添加如insts内的这些指令:

def word_csr_r(opcode, syntax, xregs, fregs, imms, symbols):
    csr = random.choice(csr_names)

    if 'pmpaddr' in csr:
        tpe = MEM_R
        insts = [ 'la xreg1, symbol',
                  'srai xreg1, xreg1, 1',
                  syntax.format(csr) ]
        symbols.append('symbol')
    else:
        tpe = CSR
        insts = [ 'xor xreg1, xreg1, xreg1']
        for i in range(random.randint(0, 3)):
            set_bits = random.choice([1, 3])
            offset = random.randint(0, 31)
            insts = insts + \
                ['addi xreg{}, zero, {}'.format(i+2, set_bits),
                 'slli xreg{}, xreg{}, {}'.format(i+2, i+2, offset),
                 'add xreg1, xreg1, xreg{}'.format(i+2)
                ]
            xregs.append('xreg{}'.format(i+2))
        insts.append(syntax.format(csr))

    return (tpe, insts)

总结PREFIX指令的生成就是:

  • 随机从rv_zicsr中选择一个opcode
  • 在opcodes_words里面匹配和其对应的指令
  • 在这个word里面添加如insts内的这些指令

2.1.1q1 rv_zicsr是什么?

在 RISC-V 架构中,zicsr 是一个标准的指令集扩展,专门用于控制和状态寄存器(CSR,Control and Status Registers)的操作。该扩展定义了一组指令,用于访问和操作这些寄存器,主要用于系统级编程和操作系统开发。

详细解释
Z:在 RISC-V 的指令集命名中,Z 表示一个标准的子集或扩展。
I:表示此扩展与整数指令集(I)相关联。
CSR:指 Control and Status Registers,也就是控制和状态寄存器。

2.1.1q2 为什么要在启动的时候调用rv_zicsr?是不是用risc-v编译.c文件的PREFIX都是rv_zicsr?

2.1.2. MAIN指令

MAIN指令则从全部指令中随机选择一个。
首先,全部的指令包括’trap_ret’,‘rv32i’,‘rv64i’,‘rv32a’,‘rv64a’,‘rv32f’,‘rv64f’,‘rv_zifencei’,‘rv_zicsr’,‘rv32m’,‘rv64m’中的总计118个指令,这些指令都会放在self.opcodes_map中。
假设我们在随机选择中选中了指令’sret’,它是最简单的指令之一,用于从操作系统的超级用户模式(Supervisor Mode)返回到用户模式(User Mode)。
对于所有的’ret’指令,都需要遵循word_ret的规则:

# ret: Return Instructions
# - 处理返回指令,如 'mret', 'sret', 'uret'
# - 指令格式: 'mret'(机器模式返回), 'sret'(超级用户模式返回), 'uret'(用户模式返回)
'ret': (['mret', 'sret', 'uret'], word_ret),
def word_ret(opcode, syntax, xregs, fregs, imms, symbols):
    tpe = CF_RET
    if syntax == 'mret': epc = 'mepc'
    elif syntax == 'sret': epc = 'sepc'
    else: epc = 'uepc'

    insts = [ 'la xreg0, symbol',
              'csrrw zero, {}, xreg0'.format(epc),
              syntax ]

    xregs.append('xreg0')
    symbols.append('symbol')

    return (tpe, insts)

2.1.3. SUFFIX

略,大概的流程和上面一样。

2.2. 使用words构建.si(simulation input?), .S(assembly), .symbols文件等

这个构建的流程大致是这样:

  • 先把之前的data(就是之前生成的random data,直接存到内存中的)存到.si文件。
  • 用现在得到的words+Template(使用的是rv64-p-u.S??)构建assembly文件(.input_1_gen.S)。
  • 用这个指令riscv64-unknown-elf-gcc -march=rv64g -mabi=lp64 -static -mcmodel=medany -fvisibility=hidden -nostdlib -nostartfiles -I Template/include -T Template/include/link.ld -I Template/include/p batch1/tests/.input_1_gen.S -o batch1/tests/.input_1_gen.elf,来构建elf文件以及symbols文件。
  • 然后用riscv64-unknown-elf-elf2hex --bit-width 64 --input batch1/tests/.input_1_gen.elf --output batch1/tests/.input_1_gen.hex,来构建hex文件。
  • 用于rtl仿真的最主要的就是hex文件了。

2.2.q1. 为什么要设置6个num_data_sections

for n in range(num_data_sections):
	start = n * section_size
	end = start + section_size
	if '_random_data{}'.format(n) in line:
	    k = 0
	    for i in range(start, end, 2):
	        label = ''
	        if i > start + 2 and i < end - 4:
	            label = 'd_{}_{}:'.format(n, k)
	            k += 1
	
	        assembly.append('{:<16}.dword 0x{:016x}, 0x{:016x}\n'.\
	                        format(label, data[i], data[i+1]))

2.3. 如何使用hex文件来进行RTL的仿真

  • 首先需要读取symbols文件里的各个变量的值,这些值标定了各个值在内存的位置。
  • 然后设置bootrom,并也把bootrom的内容load进memory中,大致的意思应该是设置一些关键的状态和地址,以便引导过程能顺利进行。这些寄存器可能包括程序计数器(PC)、堆栈指针(SP)、硬件线程 ID(hart ID)等。
  • 同时跳转到内存对应的_start位置,准备执行对应的代码:
def set_bootrom(self):
   bootrom_addrs = []
   memory = {}
   bootrom = [ 0x00000297, # auipc t0, 0x0
               0x02028593, # addi a1, t0, 32
               0xf1402573, # csrr a0, mhartid
               0x0182b283, # ld t0, 24(t0)
               0x00028067, # jr t0
               0x00000000, # no data
               0x80000000, # Jump address
               0x00000000,
               0x00000000,
               0x00000000,
               0x00000000,
               0x00000000,
               0x00000000,
               0x00000000,
               0x00000000,
               0x00000000 ] # no data

   for i in range(0, len(bootrom), 2):
       bootrom_addrs.append(0x10000 + i * 4)
       memory[0x10000 + i * 4] = (bootrom[i+1] << 32) | bootrom[i]

   return (bootrom_addrs, memory)
  • 这段代码的意思应该是把bootrom相关的代码都load进了memory中,然后记录了bootrom相关的地址,以便在启动的时候直接使用。
  • 然后经过了一段的reset等操作后之后,终于到往memory中注入程序的时候。

2.3.1. 如何用目前得到的memory进行仿真

  • 它们的框架构造了一个Rocket Tile Link的Slave,也即响应Tilelink的请求,向Tilelink发送memory内的内容的“模拟总线”。
  • 用目前获取的memory信息进行仿真的代码如下所示:
def drive_input(self, memory):
        assert memory.__class__.__name__ == 'dict', \
            'tlAdapter.drive_input need dict'

        block_perm = {}
        # TODO, check the resolution of block permissions
        for addr in set([i & self.block_mask for i in memory.keys()]):
            block_perm[addr] = TIP

        self.b_queue.clear()
        self.d_queue.clear()

        d_sink_list = [i for i in range(0, 4)]
        d_sinks = FreeList('d_sinks', d_sink_list)
        b_src_list = [i for i in range(0, 1)] # TODO, BoomTile has 3 b_src
        b_srcs = FreeList('b_srcs', b_src_list)

        b_callback = srcToCallback('b_callback', b_src_list)

        self.a_monitor = cocotb.fork(self.a_port_monitor(memory, block_perm, d_sinks, \
                                                         b_srcs, b_callback))
        self.c_monitor = cocotb.fork(self.c_port_monitor(memory, block_perm, b_srcs, \
                                                         b_callback))
        self.e_monitor = cocotb.fork(self.e_port_monitor(memory, d_sinks))

        self.d_driver = cocotb.fork(self.d_port_driver())
        self.b_driver = cocotb.fork(self.b_port_driver())

        self.retriever = cocotb.fork(self.data_retriever(block_perm, b_srcs, b_callback))
        self.host_if = cocotb.fork(self.host_interface(block_perm, b_srcs, b_callback))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值