在实现基础实施前需要的是进行源码的阅读,并且实现寄存器的模拟。实验中已经给了提示,如果需要实现寄存器的模拟需要掌握什么是匿名。只有在了解了什么是union后才可以实现寄存器的模拟,下附实现的代码(代码永远不唯一)。
typedef struct {
union{
union
{
uint32_t _32;
uint16_t _16;
uint8_t _8[2];
} gpr[8];
/* Do NOT change the order of the GPRs' definitions. */
/* In NEMU, rtlreg_t is exactly uint32_t. This makes RTL instructions
1. in PA2 able to directly access these registers.
*/
struct{
uint32_t eax;
uint32_t ecx;
uint32_t edx;
uint32_t ebx;
uint32_t esp;
uint32_t ebp;
uint32_t esi;
uint32_t edi;
};
};
vaddr_t eip;
} CPU_state;
在实现了寄存器模拟后,开始进行基础设施的搭建。
实现单步执行的功能有两个关键点:
static int cmd_si(char *args){
char *arg = strtok(args," ");
// printf("%s\n",arg);
if(arg == NULL){
printf("too few arguments.\n");
return 1;
}
int num = atoi(arg);
cpu_exec(num);
printf("OK");
return 0;
};
打印寄存器部分比较容易实现,只需要用strtok()函数将字符串分割成想要的部分即可。
static int cmd_info(char *args){
char *arg = strtok(args," ");
printf("%s\n",arg);
//cpu info
if (strcmp(arg,"r")==0){
printf("eax is %x\n",cpu.eax);
printf("ecx is %x\n",cpu.ecx);
printf("edx is %x\n",cpu.edx);
printf("ebx is %x\n",cpu.ebx);
printf("esp is %x\n",cpu.esp);
printf("ebp is %x\n",cpu.ebp);
printf("esi is %x\n",cpu.esi);
printf("edi is %x\n",cpu.edi);
printf("---------------------------\n");
}
else if(strcmp(arg,"w")==0){
print_wp(); //此部分是后期用来打印监测点状态使用,前期可以先注释掉。
}
return 0;
}
### 扫描内存
这部分直接给出代码,没有什么特别的思考逻辑。
static int cmd_x(char *args){
//获取内存起始地址和扫描长度。
if(args == NULL){
printf("too few parameter! \n");
return 1;
}
char *arg = strtok(args," ");
if(arg == NULL){
printf("too few parameter! \n");
return 1;
}
int n = atoi(arg);
char *EXPR = strtok(NULL," ");
if(EXPR == NULL){
printf("too few parameter! \n");
return 1;
}
if(strtok(NULL," ")!=NULL){
printf("too many parameter! \n");
return 1;
}
bool success = true;
//vaddr_t addr = expr(EXPR , &success);
if (success!=true){
printf("ERRO!!\n");
return 1;
}
char *str;
// vaddr_t addr = atoi(EXPR);
vaddr_t addr = strtol( EXPR,&str,16 );
// printf("%#lX\n",ad);
//进行内存扫描,每次四个字节;
for(int i = 0 ; i < n ; i++){
uint32_t data = vaddr_read(addr + i * 4,4);
printf("0x%08x " , addr + i * 4 );
for(int j =0 ; j < 4 ; j++){
printf("0x%02x " , data & 0xff);
data = data >> 8 ;
}
printf("\n");
}
return 0;
}
这个部分我用了比较长的时间,而且在做后面的实验时又对这个部分进行了补充。对于这部分,我不会贴出全部源代码,只会给出实验结果和思路。
整个表达式求值的过程我分成了两个部分:
在进行此法分析时,首先要进行的是了解正则表达式。正则表达式的用途很广,而且很神奇,最重要的是还很复杂。在此我大概说一下自己在实现这个部分是走过的坑。
第一个坑毫无疑问是正则表达式的撰写,对于这个我想说的是:多测试,尽可能想的全面。只有这样才能写出一个可以使用的正则表达式。
在完成tokens识别后需要做的是将token信息存储到tokens[]数组中,这里我使用的是strncpy()函数,因为这个函数可以更好的控制字符串复制的起始位置。
至此就已经实现一个简单的词法分析器了。
在识别并记录下全部的token信息后,可以进行求值运算了,在整个求值运算中已经有了一个完整的递归框架,这里我会详细说下其中两个功能函数
bool check_parentheses(int p ,int q){
// printf("--------------\n");
int i,tag = 0;
if(tokens[p].type != TK_LEFT || tokens[q].type != TK_RIGHT) return false; //首尾没有()则为false
for(i = p ; i <= q ; i ++){
if(tokens[i].type == TK_LEFT) tag++;
else if(tokens[i].type == TK_RIGHT) tag--;
if(tag == 0 && i < q) return false ; //(3+4)*(5+3) 返回false
}
if( tag != 0 ) return false;
return true;
}
int dominant_operator(int p , int q){
int i ,dom = p, left_n = 0;
int pr = -1 ;
for(i = p ; i <= q ; i++){
if(tokens[i].type == TK_LEFT){
left_n += 1;
i++;
while(1){
if(tokens[i].type == TK_LEFT) left_n += 1;
else if(tokens[i].type == TK_RIGHT) left_n --;
i++;
if(left_n == 0)
break;
}
if(i > q)break;
}
else if(tokens[i].type == TK_NUM10) continue;
else if(pir(tokens[i].type ) > pr){
pr = pir(tokens[i].type);
dom = i;
}
}
// printf("%d\n",left_n);
return dom;
}
其中pir是优先级函数,这里我按照c语言的标准对不同操作符赋予优先级。
在完成以上函数后,就已经实现了基本的表达式求值函数,接下来是对表达式求值的扩展- -负数和指针的识别。
for(int i = 0 ;i
在进行负数和指针的识别时要注意特殊情况 多个负号和多个指针,这里我在处理时加了一下代码:
if(tokens[op].type == TK_NEG){
for( i = op ; i 0 ;i --) result = -result;
return result;
}
else if (tokens[op].type == TK_POINT){
for( i = op ; i 0 ;i -- ) vaddr_read(result, 4);
return result;
}
这里的逻辑是统计符号的个数,然后进行相应次数的操作。
至此完成PA1.2部分。
对于实现监视点功能主要有三个部分函数(监视点所需要的结构类型,大家可以自行思考,能够实现功能即可):
//需要存入表达式和结果。
WP *new_wp(char *str , int value){
if(su == true){
init_wp_pool();
su = false;
//printf("!!!!!!!!!!!!!\n");
}
if(free_ == NULL){
printf("Erro!free is null.\n");
assert(0);
}
WP *new = NULL;
new = free_;
free_ = free_->next;
// printf("!!!!%d\n",value);
// printf("!!!!%s\n",str);
new->value = value;
// printf("!!!!%d\n",new->value);
strcpy(new->expr, str);
// printf("!!!!%d\n",new->value);
// printf("%s /n",new->expr);
new->next = NULL;
new->isused = true;
if(head == NULL) head = new;
else{
new->next = head;
head = new ;
}
return new;
}
再添加监视点前,我进行了一次判断,这个判断主要用途是判定监视点池是否完成初始化。
3. 释放监视点
void free_wp(int no){
WP *p = head;
if(head == NULL){
printf("监视点列表为空。 \n");
assert(0);
}
else if(p->NO == no){
head = head->next;
p->value = 0;
p->isused = false;
p->next = free_;
free_ = p;
printf("已经删除第%d个监视点。\n", no);
// free(p);
return;
}
else{
WP *q = head;
p = p ->next;
while(p!=NULL){
if (p->NO == no){
q->next = p->next;
p->value = 0;
p->isused = false;
p->next = free_;
free_ = p;
printf("已经删除第%d个监视点。\n", no);
// free(p);free(q);
return;
}
else{
p = p -> next;
q = q -> next;
}
}
printf("不存在第%d个监视点。\n",no);
return;
}
void print_wp(){
WP *p = head;
if(p ==NULL){
printf("监视点为空!\n");
return;
}
else{
while(p!=NULL){
printf("%d %s 0x%08x\n",p->NO , p->expr, p->value);
p=p->next;
}
return;
}
return;
}