随想录(虚拟机实现)

Posted 费晓行

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了随想录(虚拟机实现)相关的知识,希望对你有一定的参考价值。

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】

 

    要在一个cpu上实现对另外一个cpu的仿真,虚拟机是重要的方式之一。比如我们使用的cpu一般是x86或者amd64,这个时候如果需要学习arm、mips、powerpc、openrisc,或者是riscv等其他cpu,虚拟机就是重要的方式。当然,如果你野心更大一点,设计自己的cpu,这个时候也需要设计一个虚拟机。看上去设计cpu是一个硬件开发的活,但是大部分的工作其实都和软件有关,如果保证你设计的cpu硬件没有问题,这个时候虚拟机验证就是重要的一个环节。

 

    一般完成一个虚拟机需要哪几个步骤,

    1、读取bin文件,当然你也可以支持elf文件

    2、支持指令的解析,包括特殊寄存器的访问

    3、支持immu和dmmu,支持icache、dcache,此部分可选

    5、支持memory空间的解析

    6、支持外设io空间的解析

    7、中断的处理

 

    有了上面这几个部分,基本上就可以把其他cpu的程序运行起来了。甚至于说uboot、kernel、busybox编译好的系统,也是可以拿来运行的。大家要对这一点有信心。我们找一个开源的虚拟机实现来说明这一点,项目地址在这https://github.com/cassuto/nano-cpu32k-emulator,一起来看看它是怎么实现的。

    

    直接看项目的makefile文件,


CC = gcc
CFLAGS = -std=gnu99 -g -O2 -Wall -Wno-unused-function $(INCS) $(DEFS)
LDFLAGS = -g

OBJS = main-loop.o \\
		exec.o \\
		memory-mmio.o \\
		debug.o \\
		msr.o \\
		i-mmu.o \\
		d-mmu.o \\
		tsc.o \\
		irqc.o \\
		parse-symtable.o \\
		trace-runtime-stack.o \\
		device-tree.o \\
		device-ata.o
    
DEPS := $(OBJS:.o=.d)

.PHONY:all clean distclean

all: emu
clean:
	-rm $(OBJS)
distclean:
	-rm $(OBJS) $(DEPS)
	
emu: $(OBJS)
	$(CC) $(LDFLAGS) $^ -o $@
  
%.o: %.c
	$(CC) -c $(CFLAGS) $< -o $@

%.d: %.c
	$(CC) $(CFLAGS) -MM -MF $@ $<
	-@sed -i 's,\\($(notdir $*)\\)\\.o[ :]*,$(basename $@).o: ,g' '$@'


$(OBJS): $(DEPS)

-include $(DEPS)

    makefile看上去也没有几行,我们一步一步来,首先找到文件入口,也就是main-loop.c,main函数就在里面。memory_init、memory_load_address_fp、devicetree_init、cpu_exec_init初始化之后,最重要的工作就是调用cpu_exec,它位于文件exec.c。

 

    exec是一个循环函数,它的作用就是读指令,执行指令。当然,在这个过程中,有几个部分需要处理一下,一个是判断一下当前是否有时间中断和其他异常,一个是看一下指令地址是否需要翻译。等到这两个都处理好了之后,就可以处理指令了。

 

   
          case INS32_OP_AND:
            cpu_set_reg(rd, cpu_get_reg(rs1) & cpu_get_reg(rs2));
            break;

          case INS32_OP_AND_I:
            cpu_set_reg(rd, cpu_get_reg(rs1) & uimm14);
            break;
            
          case INS32_OP_OR:
            cpu_set_reg(rd, cpu_get_reg(rs1) | cpu_get_reg(rs2));
            break;

          case INS32_OP_OR_I:
            cpu_set_reg(rd, cpu_get_reg(rs1) | uimm14);
            break;
            

       其他比较重要的部分i-mmu.c,处理指令地址翻译;d-mmu.c,处理数据地址翻译;irq.c,中断处理;device-tree.c & device-ata.c,设备空间地址访问;memory-mmio.c,内存空间地址访问;msr.c,特殊寄存器读写;tsc.c,是否生成时间中断。

 

    坦率地说,支持地外设不算多,不过那是个体力活,就看自己适配多少功能了。有兴趣地同学可以看看这个代码,挺有意思的。有些软件的原理其实不复杂,不要神话linux kernel、gcc这些软件,都是可以为我们所用的,永远记住,他们都是我们使用的一个工具和手段而已。

/**
 * @brief Raise an exception to handle for CPU.
 * NOTE! you must goto handle_exception after raised an exception
 * @param [in] vector Target exception vector.
 * @param [in] lsa Target LSA virtual address.
 * @param [in] syscall Indicates if it is a syscall.
 */
void
cpu_raise_exception(vm_addr_t vector, vm_addr_t lsa, char syscall)
{
  msr.EPC = cpu_pc + (syscall ? INSN_LEN : 0);
  msr.ELSA = lsa;
  /* save old PSR */
  msr.EPSR = msr.PSR;
  /* set up new PSR for exception */
  msr.PSR.RM = 1;
  msr.PSR.IMME = 0;
  msr.PSR.DMME = 0;
  msr.PSR.IRE = 0;
  /* transfer to exception handler */
  /* -INSN_LEN will be eliminated by insn fetch in cpu_exec() */
  cpu_pc = (vm_signed_addr_t)vector -INSN_LEN;
  
#ifdef TRACE_EXCEPTION
  verbose_print_1("Raise Exception %X EPC=%x\\n", vector, msr.EPC);
  getchar();
#endif
}

    最后想强调一点异常的处理。结合计算机原理,我们看一下cpu遇到异常要实现哪些功能,现场保存哪些数据,跳转到哪些地址,pc怎么修改,这些其实都值得好好看看的。

 

 

 

以上是关于随想录(虚拟机实现)的主要内容,如果未能解决你的问题,请参考以下文章

Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段

Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段

虚拟机内存结构

JVM虚拟机

代码随想录刷题-字符串-实现 strStr()

LeetCode与《代码随想录》栈与队列篇:做题笔记与总结-JavaScript版