2 - NoraLigithub/NEMU GitHub Wiki
PA2实验报告
(一) 实验流程及错误思考 (二) 蓝框问题思考 (三) 实验总结反思
(一) 实验流程及错误思考
- 运行第一个C程序
语法错误: 未阅读明白PA2的指导语,盲目模仿mov指令的写法,但是push.c中判断指令前缀我将‘l’写作’1‘,将该写push处写作mov。 调试程序时,进行了变量的输出,甚至怀疑cpu-exec.c中出现问题,exec函数的执行始终有问题,最后也是一遍遍检查程序才找到这个傻傻的错误。 寄存器结构体组织错误: cpu.EFLAGS作为一个结构体无法直接赋值,可以采用union,。
- 实现更多指令
不能直接判断cpu.EFLAGS.OF == 1,因为标志寄存器会进行符号扩展,这是个容易忽略的bug. eip与cpu.eip的区别,helper函数返回后的值改变cpu.eip的值,再将cpu.eip给eip,比如写双字节编码的指令时,应该用eip=eip+1,并执行***(eip),前后统一,或者cpu.eip ++并执行 ***(cpu.eip)。但他们的返回值应该不同。 标志位的设置,因为大部分操作都需要修改标志位,所以考虑给同类型的指令写一个函数,初次复制了大量函数,但发现错误后也需要改,非常不便,这些错误主要是忽略部分标志位,对OF理解有误。
3.简易调试器
首先修改了PA1中的bug,实现了表达式求值,当时拖延没有调好代码的确造成巨大不便。其次是一些考虑不周的错误,未将token_type添加到case语句中等。
##(二)蓝框问题思考
编码的艺术
对于以下5个集合:
- 所有instruction prefix
- 所有address-size prefix
- 所有operand-size prefix
- 所有segment override prefix
- 所有opcode的第一个字节 它们是两两不相交的. 这是必须的吗? 这背后反映了怎样的隐情? 这是必须的。他们的功能完全不同,互不影响,如果有交叉,将对两个功能需同时实现的情况产生影响。而ModR/M字节中是否需要reg域来表示寄存器则与opcode 类型相关,故此处对于不需要reg域表示寄存器的指令,opcode可与reg域产生一些交叉。 立即数背后的故事 • 假设我们需要将NEMU运行在Motorola 68k的机器上(把NEMU的源代码编译成Motorola 68k的机器码) • 假设我们需要编写一个新的模拟器NEMU-Motorola-68k, 模拟器本身运行在x86架构中, 但它模拟的是Motorola 68k程序的执行 在这两种情况下, 你需要注意些什么问题? 为什么会产生这些问题? 怎么解决它们? 事实上不仅仅是立即数的访问, 长度大于1字节的内存访问都需要考虑类似的问题. 我们在这里把问题统一抛出来, 以后就不再单独讨论了. 第一种情况:Motorola68k是按照大端方式解释字节序列,所以我们读字节序列不能按照原来的方式读,应该先instr_fetch(eip+1,4)读出字节序列,再按照大端方式解释字节。 第二种情况:读出字节序列后,再按小端方式解释。
消失的符号
我们在add.c中定义了宏NR_DATA, 同时也在add()函数中定义了局部变量c和形参a, b, 但你会发现在符号表中找不到和它们对应的表项, 为什么会这样? 思考一下, 什么才算是一个符号(symbol)? 在程序中被定义和引用的函数名和全局变量属于 symbol,宏NR_DATA预编译已经替换掉了,所以不是符号,局部变量和形参不是全局变量,他们的空间分配在栈桢中,也不是symbol。 符号是内存和字符串的一种映射,符号包括一些函数名,是因为这种映射可以是一段较大空间的映射。由此解释了宏不需要这种映射,局部变量为节省空间不需要固定的映射,可在函数中分配。
寻找"Hello World!"
在GNU/Linux下编写一个Hello World程序, 编译后通过上述方法找到ELF文件的字符串表, 你发现"Hello World!"字符串在字符串表中的什么位置? 为什么会这样? 在_bss_start 前
丢失的信息
在测试程序中定义以下字符数组: char str[] = "abcdefg"; 输出的是“abcde”以小端方式排列的无符号整数值,因为表达式求值并没有判断所求的值的类型的功能,不能以字符串形式输出。
冗余的符号表
符号表在程序执行时并没有起到什么作用,但重定位时需要用到,所以丢弃信息后不能正确链接
堆栈在哪里?
我们提到了代码和数据都在可执行文件里面, 但却没有提到堆栈. 为什么堆栈的内容没有放入可执行文件里面? 那程序运行时刻用到的堆栈又是怎么来的? 堆栈是在程序执行的时动态分配的,因此不会放入可执行文件堆栈是指以EBP为栈底,ESP为栈顶的一段内存空间。 如何识别不同格式的可执行文件? 如果你在GNU/Linux下执行一个从Windows拷过来的可执行文件, 将会报告"格式错误". 思考一下, GNU/Linux是如何知道"格式错误"的? 执行时linux会按照elf的格式解释可执行文件,如果格式不匹配,无法执行。
冗余的属性?
使用readelf查看一个ELF文件的信息, 你会看到一个segment包含两个大小的属性, 分别是FileSiz和MemSiz, 这是为什么? 再仔细观察一下, 你会发现FileSiz通常不会大于相应的MemSiz, 这又是为什么? FileSiz,程序段的内容占内存,即应该被加载的内存大小。MemSiz还包括.bss节的内容。所以FileSiz小一些。
为什么要清零?
为什么需要将[VirtAddr + FileSiz, VirtAddr + MemSiz)对应的物理区间清零? .bss节的内容从ELF文件中读入到( VirtAddr + FileSiz, VirtAddr + MemSiz)这一区间,如果不将[VirtAddr + FileSiz, VirtAddr
- MemSiz)清零,会影响程序的执行的。
(三)实验总结反思
1.写指令时极易陷入各种小错,难以发现,难以检测,继续完善指令的过程中应该做好大体规划再开始,特别是对于标志位的设置等常见项目,提高效率。
2.反复阅读讲义才发现第一次读时不懂忽略的东西极多。而我在比较长时间不再阅读讲义,走很多弯路。
3.合理安排时间,写的时候极容易陷入时间的消耗,而不是高效率完成。