ICS2017 Programming Assignment
  • Introduction
  • PA0 - 世界诞生的前夜: 开发环境配置
    • Installing a GNU/Linux VM
    • First Exploration with GNU/Linux
    • Installing Tools
    • Configuring vim
    • More Exploration
    • Transferring Files between host and container
    • Acquiring Source Code for PAs
  • PA1 - 开天辟地的篇章: 最简单的计算机
    • 在开始愉快的PA之旅之前
    • 开天辟地的篇章
    • RTFSC
    • 基础设施
    • 表达式求值
    • 监视点
    • i386手册
  • PA2 - 简单复杂的机器: 冯诺依曼计算机系统
    • 不停计算的机器
    • RTFSC(2)
    • 程序, 运行时环境与AM
    • 基础设施(2)
    • 输入输出
  • PA3 - 穿越时空的旅程: 异常控制流
    • 更方便的运行时环境
    • 等级森严的制度
    • 穿越时空的旅程
    • 文件系统
    • 一切皆文件
  • PA4 - 虚实交错的魔法: 分时多任务
    • 虚实交错的魔法
    • 超越容量的界限
    • 分时多任务
    • 来自外部的声音
    • 编写不朽的传奇
  • PA5 - 从一到无穷大: 程序与性能
    • 浮点数的支持
    • 通往高速的次元
    • 天下武功唯快不破
  • 杂项
    • 为什么要学习计算机系统基础
    • 实验提交要求
    • Linux入门教程
    • man入门教程
    • git入门教程
    • i386手册指令集阅读指南
    • i386手册勘误
    • 指令执行例子
Powered by GitBook
On this page
  • 解析命令
  • 单步执行
  • 打印寄存器
  • 扫描内存

Was this helpful?

  1. PA1 - 开天辟地的篇章: 最简单的计算机

基础设施

简易调试器是NEMU中一项非常重要的基础设施. 我们知道NEMU是一个用来执行其它客户程序的程序, 这意味着, NEMU可以随时了解客户程序执行的所有信息. 然而这些信息对外面的调试器(例如GDB)来说, 是不容易获取的. 例如在通过GDB调试NEMU的时候, 你将很难在NEMU中运行的客户程序中设置断点, 但对于NEMU来说, 这是一件不太困难的事情.

为了提高调试的效率, 同时也作为熟悉框架代码的练习, 我们需要在monitor中实现一个具有如下功能的简易调试器 (相关部分的代码在nemu/src/monitor/debug目录下), 如果你不清楚命令的格式和功能, 请参考如下表格:

命令

格式

使用举例

说明

帮助(1)

help

help

打印命令的帮助信息

继续运行(1)

c

c

继续运行被暂停的程序

退出(1)

q

q

退出NEMU

单步执行

si [N]

si 10

让程序单步执行N条指令后暂停执行, 当N没有给出时, 缺省为1

打印程序状态

info SUBCMD

info r info w

打印寄存器状态 打印监视点信息

表达式求值

p EXPR

p $eax + 1

扫描内存(2)

x N EXPR

x 10 $esp

求出表达式EXPR的值, 将结果作为起始内存 地址, 以十六进制形式输出连续的N个4字节

设置监视点

w EXPR

w *0x2000

当表达式EXPR的值发生变化时, 暂停程序执行

删除监视点

d N

d 2

删除序号为N的监视点

备注:

  • (1) 命令已实现

  • (2) 与GDB相比, 我们在这里做了简化, 更改了命令的格式

解析命令

NEMU通过readline库与用户交互, 使用readline()函数从键盘上读入命令. 与gets()相比, readline()提供了"行编辑"的功能, 最常用的功能就是通过上, 下方向键翻阅历史记录. 事实上, shell程序就是通过readline()读入命令的. 关于readline()的功能和返回值等信息, 请查阅

man readline

从键盘上读入命令后, NEMU需要解析该命令, 然后执行相关的操作. 解析命令的目的是识别命令中的参数, 例如在si 10的命令中识别出si和10, 从而得知这是一条单步执行10条指令的命令. 解析命令的工作是通过一系列的字符串处理函数来完成的, 例如框架代码中的strtok(). strtok()是C语言中的标准库函数, 如果你从来没有使用过strtok(), 并且打算继续使用框架代码中的strtok()来进行命令的解析, 请务必查阅

man strtok

另外, cmd_help()函数中也给出了使用strtok()的例子. 事实上, 字符串处理函数有很多, 键入以下内容:

man 3 str<TAB><TAB>

其中<TAB>代表键盘上的TAB键. 你会看到很多以str开头的函数, 其中有你应该很熟悉的strlen(), strcpy()等函数. 你最好都先看看这些字符串处理函数的manual page, 了解一下它们的功能, 因为你很可能会用到其中的某些函数来帮助你解析命令. 当然你也可以编写你自己的字符串处理函数来解析命令.

另外一个值得推荐的字符串处理函数是sscanf(), 它的功能和scanf()很类似, 不同的是sscanf()可以从字符串中读入格式化的内容, 使用它有时候可以很方便地实现字符串的解析. 如果你从来没有使用过它们, RTFM, 或者到互联网上查阅相关资料.

单步执行

单步执行的功能十分简单, 而且框架代码中已经给出了模拟CPU执行方式的函数, 你只要使用相应的参数去调用它就可以了. 如果你仍然不知道要怎么做, RTFSC.

打印寄存器

打印寄存器就更简单了, 执行info r之后, 直接用printf()输出所有寄存器的值即可. 如果你从来没有使用过printf(), 请到互联网上搜索相关资料. 如果你不知道要输出什么, 你可以参考GDB中的输出.

扫描内存

扫描内存的实现也不难, 对命令进行解析之后, 先求出表达式的值. 但你还没有实现表达式求值的功能, 现在可以先实现一个简单的版本: 规定表达式EXPR中只能是一个十六进制数, 例如

x 10 0x100000

这样的简化可以让你暂时不必纠缠于表达式求值的细节. 解析出待扫描内存的起始地址之后, 你就使用循环将指定长度的内存数据通过十六进制打印出来. 如果你不知道要怎么输出, 同样的, 你可以参考GDB中的输出.

实现了扫描内存的功能之后, 你可以打印0x100000附近的内存, 你应该会看到程序的代码, 和默认镜像进行对比, 看看你的实现是否正确.

PreviousRTFSCNext表达式求值

Last updated 6 years ago

Was this helpful?

求出表达式EXPR的值, EXPR支持的 运算请见小节

调试中的表达式求值