NEMU PA1

RTFSC

优美地退出

这一部分主要靠RTFSC,查看nemu-main.c,了解程序的整个运行过程,定位在is_exit_status_bad()函数,此时可以通过printf("%d\n", !good)查看函数返回值,当直接键入q退出时,函数返回值为1,锁定问题,现在只要更改函数返回值(即更改good的值)就可以了。结合good定义,当键入q时,将nemu_state.state设为NEMU_QUIT,即可将问题解决。

根据 C 语言标准,main 函数的返回值为 int 类型,通常约定:
返回值为 0 表示程序正常结束。
返回值为非零整数(通常为 1 或其他非零值)表示程序异常结束或出错。

基础设施

单步执行

格式: si [N]
函数: strtok、atoi

实现比较简单,直接附代码:

static int cmd_si(char *args)
{
	char *arg = strtok(NULL, " ");
	int num_line = 0;

	if(arg == NULL)
	{
		cpu_exec(1);
	}
	else
	{
		num_line = atoi(arg);
		if(num_line == 0)
		{
			printf("Unknown input, the standard format is 'si [N]'\n");
			return 0;
		}
		cpu_exec(num_line);	
	}
	
	return 0;
}

打印寄存器

格式: info r
函数: strtok

文档中已经给了我们很明确地提示,当输入info r时,只需要调用APIisa_reg_display(),在isa_reg_display()中我们实现输出所有寄存器的值。
首先,添加cmd_info函数:

static int cmd_info(char *args)
{
	char *arg = strtok(NULL, " ");

	if( strcmp(arg, "r") == 0)
		isa_reg_display();
    else if( strcmp(arg, "w") == 0)
    display_wp();
	else
		printf("Unknown input, the standard format is 'info SUBCMD'\n");

	return 0;
}

然后再实现isa_reg_display(),在实现这个函数时,我们需要RTFSC找到寄存器的值保存在哪里,通过一番搜索,不难发现,寄存器值保存在结构体成员gpr中。

void isa_reg_display() {
	//bool success = true;
	int count = 0;
	for(int i = 0; i < 32; i++)
	{
		printf("%s \t 0x%08x\t", reg_name(i), cpu.gpr[i]);
		count++;
		if(count == 3 || i == 31)
		{
			printf("\n");
			count = 0;
		}
	}
}

扫描内存

格式: x N EXPR
函数: strtok、strtol
ANSI_FMT可用于设置终端输出文本的颜色

想要实现扫描内存的功能,需要使用到memory中的vaddr_read来读取内存中的值,每次读取四个字节。

static int cmd_x(char *args)
{
	char *arg1 = strtok(NULL, " ");
	
	if(arg1 == NULL)
	{
		printf("Unknown input, the standard format is 'x N EXPR'\n");
		return 0;
	}

	char *arg2 = strtok(NULL, " ");

	if(arg2 == NULL)
	{
		printf("Unknown input, the standard format is 'x N EXPR'\n");
		return 0;
	}

	int n = strtol(arg1, NULL, 10);
	vaddr_t base_addr =  strtol(arg2, NULL, 16);

	for(int i = 0; i < n;)
	{
		printf(ANSI_FMT("0x%08x: ", ANSI_FG_BLUE), base_addr);
		for(int j = 0; i < n && j < 4; i++,j++)
		{
			word_t data = vaddr_read(base_addr, 4);
			printf("0x%08x\t", data);
		  base_addr += 4;	
		}
		printf("\n");
	}
	return 0;
}

表达式求值

格式: p EXPR
函数: strncpy、strtol、strncmp

词法分析

实现词法分析之前,首先要学习一下正则表达式的规则,在这里特别需要注意寄存器的识别,因为在寄存器数组中包含一个$0的寄存器,所以出现$$$都有可能是寄存器。

首先,定义tokens可能的类型,并将token类型分类,以便后面用到。OFTYPES宏用于判断某个token的类型是否属于该类

enum {
	TK_NOTYPE = 256,

  /* TODO: Add more token types */

	TK_NEG, TK_POS, TK_DEREF,                   // + - *
	TK_EQ, TK_NEQ, TK_GT, TK_LT, TK_GE, TK_LE,  // == != > < >= <= 
	TK_AND, TK_OR,                              //&& ||
	TK_NUM,	
	TK_REG,
};
#define OFTYPES(type, types) oftypes(type, types, ARRLEN(types))

static int bound_types[] = {')', TK_NUM, TK_REG};
static int nop_types[] = {'(', ')', TK_NUM, TK_REG};
//static int uo_types[] = {TK_NEG, TK_POS, TK_DEREF}; //Unary operator

static bool oftypes(int type, int types[], int size)
{
	for(int i = 0; i < size; i++)
		if(type == types[i]) return true;
	return false;
}

然后再用正则表达式去匹配这些类型:

static struct rule {
  const char *regex;
  int token_type;
} rules[] = {

  /* TODO: Add more rules.
   * Pay attention to the precedence level of different rules.
   */

	{" +", TK_NOTYPE},    // spaces
	
	{"\\(", '('}, {"\\)", ')'},
	{"\\+", '+'},	{"\\-", '-'},
	{"\\*", '*'}, {"\\/", '/'},
	{"<", TK_LT}, {">", TK_GT}, {"<=", TK_LE}, {">=", TK_GE},
	{"==", TK_EQ}, {"!=", TK_NEQ},
	{"&&", TK_AND}, {"\\|\\|", TK_OR},

	{"(0[xX][0-9A-Fa-f]+|\\b[0-9]+\\b)", TK_NUM},
	//{"(0x)?[0-9]+", TK_NUM},
	{"\\${1,2}\\w+", TK_REG},
};

最后,在make_token()函数中添加代码,实现tokens数组的生成(tokens数组用于按顺序存放已经被识别出的token信息)

static bool make_token(char *e) {
  int position = 0;
  int i;
  regmatch_t pmatch;

  nr_token = 0;

  while (e[position] != '\0') {
    /* Try all rules one by one. */
    for (i = 0; i < NR_REGEX; i ++) {
      if (regexec(&re[i], e + position, 1, &pmatch, 0) == 0 && pmatch.rm_so == 0) {
        char *substr_start = e + position;
        int substr_len = pmatch.rm_eo;

		//用于显示匹配信息
        //Log("match rules[%d] = \"%s\" at position %d with len %d: %.*s",
        //    i, rules[i].regex, position, substr_len, substr_len, substr_start);

        position += substr_len;

        /* TODO: Now a new token is recognized with rules[i]. Add codes
         * to record the token in the array `tokens'. For certain types
         * of tokens, some extra actions should be performed.
         */
				
		if(rules[i].token_type == 256) break;

		tokens[nr_token].type = rules[i].token_type;  //在前面已经匹配过
        switch (rules[i].token_type) {
			case TK_NUM:
			case TK_REG:
				strncpy(tokens[nr_token].str, substr_start, substr_len);
				tokens[nr_token].str[substr_len] = '\0';
				break;
			case '*': case '-': case '+':
				if(nr_token == 0 || !OFTYPES(tokens[nr_token - 1].type, bound_types))
				{
					switch(rules[i].token_type)
					{
						case '-': tokens[nr_token].type = TK_NEG; break;
						case '+': tokens[nr_token].type = TK_POS; break;
						case '*': tokens[nr_token].type = TK_DEREF; break;
					}
				}				
				break;
        }
		nr_token++;
        break;
      }
    }

    if (i == NR_REGEX) {
      printf("no match at position %d\n%s\n%*.s^\n", position, e, position, "");
      return false;
    }
  } 

  return true;
}

递归求值

词法分析的功能实现后,待求值表达式中所有token的类型都被保存在tokens数组中,接着根据文档的提示写出求值函数eval()

word_t eval(int p, int q, bool *success)
{
	*success = true;
	if(p > q)
	{
		*success = false;
		return 0;
	}
	else if(p == q)
	{
		if(tokens[p].type != TK_NUM && tokens[p].type != TK_REG) //&&误写为||,debug了好久
		{
			*success = false;
			return 0;
		}
		if(tokens[p].type == TK_NUM)
		{
			if(strncmp("0x", tokens[p].str, 2) == 0 || strncmp("0X", tokens[p].str, 2) == 0)   //16
				return strtol(tokens[p].str, NULL, 16);
			else
				return strtol(tokens[p].str, NULL, 10);    //10
		}
		else
		{
			return isa_reg_str2val(tokens[p].str, success);  //reg
		}
	}
	else if(check_parentheses(p, q) == true)
	{ 
		return eval(p + 1, q - 1, success);
	}
	else
	{
		int op = find(p, q);
		if(op < 0)
		{
			*success = false;
			return 0;
		}
		//printf("2\n");
		bool success1, success2;
		word_t val1 = eval(p, op - 1, &success1);
		//if(!*success) return 0;
		word_t val2 = eval(op + 1, q, &success2);
		//if(!*success) return 0;
		
		if(!success2)
		{
			//printf("4\n");
			*success = false;
			return 0;
		}
		if(!success1)
		{
			//printf("2\n");
			switch(tokens[op].type)
			{
				case TK_NEG: return -val2;
				case TK_POS: return val2;
				case TK_DEREF: return vaddr_read(val2, 4);
				default: *success = false;
								 return 0;
			}
		}
		else
		{
			//printf("3\n");
			switch(tokens[op].type)
			{
				case '+': return val1 + val2;
				case '-': return val1 - val2;
				case '*': return val1 * val2;
				case '/': if(val2 == 0)
									{
										*success = false;
										return 0;
									}
									return (sword_t)val1 / (sword_t)val2;
				case TK_EQ: return val1 == val2;
				case TK_NEQ: return val1 != val2;
				case TK_GT: return val1 > val2;
				case TK_LT: return val1 < val2;
				case TK_GE:	return val1 >= val2;
				case TK_LE: return val1 <= val2;
				case TK_AND: return val1 && val2;
				case TK_OR: return val1 || val2;
				default: *success = false; return 0;
			}
		}
	}
	return 0; //without it, then will error
}

debug:在p == q判断里刚开始将&&写为了||,后续debug了挺久

然后实现check_parentheses()函数,用于判断表达式是否被一对匹配的括号包围着

bool check_parentheses(int p, int q)
{
	if(tokens[p].type == '(' && tokens[q].type == ')')
	{
		int par = 0;
		for(int i = p; i <= q; i++)
		{
			if(tokens[i].type == '(') par++;
			else if(tokens[i].type == ')') par--;
			if(par == 0) return i == q;
		}
	}
	return false;
}

最后实现find()函数来寻找主运算符,主算符的特征如下:

  • 非运算符的token不是主运算符
  • 主运算符不会出现在括号内
  • 主运算符的优先级在表达式中是最低的
  • 当有多个运算符优先级都是最低的时,最右边的才是主运算符
int find(int p, int q)
{
	int par = 0, op_type = 0, pos = 0;
	//printf("1\n");
	for(int i = p; i <= q; i++)
	{ 
		if(tokens[i].type == TK_NUM)
			continue;
		else if(tokens[i].type == '(')
			par++;
		else if(tokens[i].type == ')')
		 {
			if(par == 0) return -1;
			par--;
		}
		else if(OFTYPES(tokens[i].type, nop_types))
			continue;
		else
		{ 
			if(par > 0) continue;
			int tmp_type;    //是主运算符的优先级
			switch(tokens[i].type)
			{
				case TK_NEG: case TK_POS: case TK_DEREF: tmp_type = 1; break;
				case '*': case '/': tmp_type = 2; break;
				case '+': case '-': tmp_type = 3; break;
				case TK_GT: case TK_LT: case TK_GE: case TK_LE: tmp_type = 4; break;
				case TK_EQ: case TK_NEQ: tmp_type = 5; break;
				case TK_AND: tmp_type = 6; break;
				case TK_OR: tmp_type = 7; break;
				default: assert(0);	
			 }
			if(tmp_type > op_type)
			{
				op_type = tmp_type;
				pos = i;
			} 
		}

	}
	if(par != 0) return -1;
  //printf("%d\n", pos);
	return pos;
}

代码测试

根据文档提示,写出生成表达式的框架:

static void gen_rand_expr() {
  //buf[0] = '\0';
	if(index_buf > 65530)
		printf("oversize\n");
	switch(choose(3))
	{
		case 0: gen_num(); break;
		case 1: gen('('); gen_rand_expr(); gen(')'); break;
		default: gen_rand_expr(); gen_rand_op(); gen_rand_expr(); break;
	}
}

然后再分别实现gen_num()、gen()、gen_rand_op()、choose()函数,这几个函数实现都比较简单

int choose(int n)
{
	return rand() % n;
}

static void gen(char c)
{
	buf[index_buf++] = c;
}

static void gen_num()
{
	int num = rand() % 100;
	//if(num == 0) num += 1;
	int len = 0, tmp = num;
	while(tmp)
	{
		tmp /= 10;
		len++;
	}

	int x;

	if(len <= 1) x = 1;
	else x = (len - 1) * 10;

	while(num)
	{
		char c = num / x + '0';
		buf[index_buf++] = c;
		num %= x;
		x /= 10;
	}
}

static void gen_rand_op()
{
	char op[4] = {'+', '-', '*', '/'};
	int pos = rand() % 4;
	buf[index_buf++] = op[pos];
}

notice:

  • 过滤除0表达式的方式就是粗暴地开启-Wall -Werror参数,因为生成的表达式属于字面量(literal constant),编译时就会计算表达式,如果含有除0的情况gcc就会执行失败,所以可以通过system返回值检测编译是否含有除0的情况
  • main函数中每次循环都会生成一个表达式,要注意将index_buf置为0,并将buf数组清空
  • 使用./gen-expr 10000 > input生成一些测试用例时,可能会出错,但是并不影响,可以查看input文件中,正确生成并计算的测试用例结果是否准确

监视点

实现监视点的管理

首先完成监视点结构体的补充:

typedef struct watchpoint {
  int NO;
  struct watchpoint *next;

  /* TODO: Add more members if necessary */

  word_t old_value;
  char expr[100];         // ** char *expr is error ** //
} WP;

接下来实现new_wp()free_wp()函数,其中new_wp()表示从free_链表中返回一个空闲的监视点结构, free_wp()wp归还到free_链表中

调用new_wp()时可能会出现没有空闲监视点结构的情况,此类情况很好的一种解决办法是直接使用assert(free_)

debug: 在WP结构体中,expr应定义为expr数组,而不是expr指针

WP* new_wp()
{
  if(free_ == NULL)
  {
    printf("Unused watchpoint\n");
    assert(0);
  } 
  WP* tmp = free_;
  free_ = free_->next;
  tmp->next = head;
  head = tmp;
  return tmp;
}

void free_wp(WP *wp)
{
  if(wp == NULL)
  {
    printf("No watchpoints are using\n");
    assert(0);
  }
  if(wp->next == NULL)
  {
    wp->next = free_;
    free_ = wp;
    head = NULL;
    return;
  }
  else if(wp->next != NULL && wp == head)
  {
    head = wp->next;
    wp->next = free_;
    free_ = wp;
    return;
  }
  WP *tmp = head;
  while(tmp->next)
  {
    if(tmp->next == wp)
      break;
    tmp = tmp->next;
  }
  tmp->next = wp->next;
  wp->next = free_;
  free_ = wp;
}

设置监视点

static int cmd_w(char *args)
{
  if(args == NULL)
  {
    printf("Unknown input, the standard format is 'w EXPR'\n");
    return 0;
  }
  bool success;
  word_t res = expr(args, &success);
  if(!success)
    printf("The expression is problematic\n");
  else 
    set_wp(args, res);
  
  return 0;
}
void set_wp(char *arg, word_t value)   //set the watchpoint
{
  WP *new = new_wp();
  new->old_value = value;
  strcpy(new->expr, arg);
  printf("Hardware watchpoint %d: %s\n", new->NO, new->expr);
}

扫描监视点

文档说明的很详细,根据文档提示即可。

void scan_wp()    //scan all watchpoints
{
  WP *tmp = head;
  //printf("hello\n");
  while(tmp)
  {
    bool success;
    word_t new_value = expr(tmp->expr, &success);
    //printf("hello\n");
    if(new_value != tmp->old_value)
    {
      printf("The watchpoint %d: %s has changed\n", tmp->NO, tmp->expr);
      printf("Old value = %u\n", tmp->old_value);
      printf("New value = %u\n", new_value);
      tmp->old_value = new_value;
      nemu_state.state = NEMU_STOP;
      return;
    }
    tmp = tmp->next;
  }
  //return 0;
}

打印监视点信息 info w

其实就是遍历一下head链表,输出相关信息

void display_wp()   //display all watchpoints
{
  WP *tmp = head;
  if(tmp == NULL)
    printf("No watchpoints.\n");
  else
  {
    printf("%-20s%-20s%-20s\n", "Num", "What", "Value");
    while(tmp)
    {
      printf("%-20d%-20s%-20u\n", tmp->NO, tmp->expr, tmp->old_value);
      tmp = tmp->next;
    }
  }
}

删除监视点

根据序号在head链表中找到对应的监视点,然后调用free_wp()即可。

static int cmd_d(char *args)
{
  if(args == NULL)
  {
    printf("Unknown input, the standard format is 'd N'\n");
    return 0;
  }
  char *arg = strtok(NULL, " ");
  int n = strtol(arg, NULL, 10);
  delete_wp(n);
  return 0;
}
void delete_wp(int n)  //delete the watchpoint, its NO is n.
{
  WP *tmp = head;
  if(tmp == NULL)
    printf("The watchpoint for which NO is %d does't exist.\n", n);
  else
  {
    while(tmp->NO != n)
      tmp = tmp->next;
    if(tmp == NULL)
      printf("The watchpoint for which NO is %d does't exist.\n", n);
    else
    {
      free_wp(tmp);
      printf("Deleted success\n");
    }
  }
}

统计代码行数

count:
	@echo "The total number of lines in all the .c and .h files in nemu is: "
	@find . -type f \( -name "*.c" -o -name "*.h" \) -exec cat {} + | wc -l

count_no_space:
	@echo "The total number of lines in all the .c and .h files in nemu without spaces is: "
	@find . -type f \( -name "*.c" -o -name "*.h" \) -exec awk 'NF > 0' {} + | wc -l

SUMMARY

函数介绍

strtok

char *strtok(char *str, const char *delim)

函数的作用是从字符串 str 中提取标记,每次调用都会返回下一个标记。参数 delim 是一个包含分隔符字符的字符串,用于指定在哪些字符处进行分割。

PA1中用的最多的就是strtok(NULL, " "),由于传入的参数是args,第一次调用 strtok(NULL, " ") 会在 args 中查找下一个空格,并将空格替换为 \0。这个调用返回指向第一个标记的指针(即空格之前的部分)。之后的调用 strtok(NULL, " ") 会继续在同一字符串中查找,从上一次找到的标记的下一个位置开始。它会继续查找下一个空格,并将空格替换为 \0

strtol

long strtol(const char *nptr, char **endptr, int base)

  • nptr:指向要转换的字符串的指针。
  • endptr:用于存储遇到的第一个非法字符的指针,如果该参数不为 NULL。如果整个字符串都是合法的数字,endptr 将被设置为 nptr + strlen(nptr)
  • base:指定进制,可以是 2、8、10(默认)、16 等。

可以搭配strtok一起使用

	char *arg = strtok(NULL, " ");
	int n = strtol(arg, NULL, 10); 

strcmp

int strcmp(const char *s1, const char *s2)

  • 如果 s1 的字典顺序在 s2 之前,则返回负数。
  • 如果 s1s2 相等,则返回零。
  • 如果 s1 的字典顺序在 s2 之后,则返回正数。

debug:这个函数也帮助我解决了一个bug。在表达式求寄存器的值时,例如输入p $sp,此时会调用isa_reg_str2val(const char *s, bool *success)函数,注意此时传入的*s$sp,而不是sp,所以在将输入的寄存器与寄存器数组进行匹配时,要使用到strcmp(regs[i], s + 1)

word_t isa_reg_str2val(const char *s, bool *success) {
	*success = true;
	bool sign = false;
	int i;
	//printf("%s", s);
	for(i = 0; i < 32; i++)
	{
		if(strcmp(regs[i], s + 1) == 0)  //** s + 1 , but isn't s **
		{
			sign = true;
			break;
		}
	}
	if(!sign)
	{
		*success = false;
		printf("The register is not exist\n");
		return 0;
	}
	else 
	{
		return cpu.gpr[i];
	}

}

strcpy

char *strcpy(char *dest, const char *src)

dest:目标字符串的指针,接收源字符串的内容。
src:源字符串的指针,提供要复制的内容。

即将src复制到dest

strncpy

char *strncpy(char *dest, const char *src, size_t n)

dest:目标字符串的指针,接收源字符串的内容。
src:源字符串的指针,提供要复制的内容。
n:最多复制的字符数。

strncmp

int strncmp(const char *s1, const char *s2, size_t n)

  • s1s2 分别是要比较的两个字符串的指针。
  • n:最多比较的字符数。

如果 s1 的前 n 个字符的字典顺序在 s2 的前 n 个字符之前,则返回负数。
如果 s1s2 的前 n 个字符相等,则返回零。
如果 s1 的前 n 个字符的字典顺序在 s2 的前 n 个字符之后,则返回正数。

PA1使用:if(strncmp("0x", tokens[p].str, 2) == 0 || strncmp("0X", tokens[p].str, 2) == 0)

MUXDEF

#define COND 1
MUXDEF(COND, puts("hello"), puts("world")); 
// 展开后如下
printf("hello");

//相当于
#define COND 1
//注意COND只要被定义,就会输出hello,不管值为0或1
#ifdef COND
	puts("hello");
#else
	 puts("world");
#endif

IFDEF

#define COND 1
IFDEF(COND, puts("hello")); 
// 展开后如下
printf("hello");

//相当于
#define COND 1
//注意COND只要被定义,就会输出hello,不管值为0或1
#ifdef COND
	puts("hello");
#endif

当然,最重要的还是STFWRTFMRTFSC

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值