计算机系统基础PA1

在开始愉快的PA之旅之前

 

 不来玩一下吗?

   不得不说,要仔细看清楚来,是根据 fceux-am/README.md中的内容进行操作

  这里我们可以拿到一个压缩包

  咋解压?

    

   解压完后,比如我想要将这个文件夹下的全部文件移动到当前文件夹下,咋办?

  如将 a/b/下的文件全部移动搭配 a/下

  mv a/b/* a/

 

解决编译有点慢的问题

  lscpu查看系统有多少个cpu

    因为make 可以多线程运行,但是默认情况下是单线程运行

    使用 make -j? (?是查到的cpu个数)可以多线程运行

 

    使用Ccache

    
    为了配置Ccache 我们来看看man中对与 Ccache是咋说的吧!

      

   我们就用第一种方法吧

    这个时候就有个难题了,什么叫做 just make sure that ccache is in your PATH,应该怎么做?

 

    PATH是什么?

    

   

   环境变量是什么?

   

   

 

  我们咋做?

   

 

   通过which ccache 可以知道cccahe安装在哪里

   

  我们在~/.bashrc中找到PATH并改成

  export PATH="/usr/bin/ccache:$PATH"

 

  .bashrc是什么?

     好博客<-----

   

 

NEUM是什么?

   neum是可以运行程序的程序

   

   

 

ISA是什么?

   

 

 

 

 

开天辟地的篇章

  

 

 

 

RTFSC

  nemu 就是我们用程序模拟出来的电脑

   

  当我们运行程序时,nemu能够达到如上效果

 

    nemu中模仿了计算机,那nemu中的结构时咋样的?

   

   

 

Makefile入门资料<-----

C语言条件编译#if入门资料<----

C语言中为何需要.h文件,其作用是什么?<------

 

   当然是main开始 

     

    

    

    我们先捋清我们启动这个nemu的步骤:

  1.首先我们需要设置nemu的配置:

     

     

    2.然后我们需要编译:

    

     

 

     3.最后我们可以启动nemu了:

     

 

     更多关于配置和编译上代码的细节需要看RTFSC中的 配置系统和项目构建

 

    我们直接讨论一下make run 后发生了什么:

    如下为nemu-main.c的代码:

    

 

    可以看到其是要初始化monitor

那么monitor是什么?他要干什么?

       

       即monitor其初始化是为运行客户程序做准备

       在init_monitor()中 一系列初始化的函数:

       

      

  在init_isa()函数中:

    1.第一项工作就是将一个内置的客户程序读入到内存中. 

      

       

 

    2.第二项任务是初始化寄存器, 这是通过restart()函数来实现的.

       初始化寄存器的一个重要工作就是设置cpu.pc的初值, 我们需要将它设置成刚才加载客户程序的内存位置,

       这样就可以让CPU从我们约定的内存位置开始执行客户程序了. 

      

         

 

           

 

 

运行第一个客户程序

 

  Monitor的初始化工作结束后, main()函数会继续调用engine_start()函数(在nemu/src/engine/interpreter/init.c中定义).

    代码会进入简易调试器(Simple Debugger)的主循环sdb_mainloop() (在nemu/src/monitor/sdb/sdb.c中定义),

  并输出NEMU的命令提示符:

 

     

    至此,我们的第一个客户程序便运行起来了

 

 

    

    cmd_c()函数在~/ics2022/nemu/src/monitor/sdb/sdb.c 中

     

 

    cpu_exec()函数在/ics2022/nemu/src/cpu/cpu-exec.c中

   

     

    在cpu_exec()中主要是会调用execute()函数

     

     注意这里的n是uint64_t 即无符号int数

    当传值为-1时,根据补码规则,-1是最大的无符号整数,即这里会循环完整个无符号整数的范围

    

     

     

     看来并不是return 后程序就结束了,还需要一些函数来做一些善后处理

     

 

 

   

 

 

对于调试有用的宏

   

     下面用蓝色字体打印出来的为调用Log()输出的,而白色字体的为普通调用C库函数的printf等

     

    下面是使用Log的源码:

    可以看出确实使用Log()可以自动打印出在哪个代码下的哪个函数的哪一行中

     

     想要看懂Log实现的源代码不得不学一下宏是咋用的了

    

 

宏与条件编译

   

   非常不错的宏入门教程<-----

  现在来看看Log实现的源码:

#define Log(format, ...) \                                                        
      _Log(ANSI_FMT("[%s:%d %s] " format, ANSI_FG_BLUE) "\n", \                                           
    __FILE__, __LINE__, __func__, ## __VA_ARGS__)

 

  

   

 

 

  

   更多的可变长参数在上面的博客中

   

 

优美地退出

   我的经历比较曲折,当时没办法了,我想知道如何直接从nemu-main.c开始调试,但是不知道咋整

   但是不知道从哪个文件看到的(上面讲义提到过),应该是在一个.mk或者Makefile上看到的

   make gdb可以从nemu-main.c开始调试

   于是我开始了调试之旅

   出现 Error 1这样的报错 我开始看的是一头雾水,但是随着调试的进行

   出现了return is_exit_status_bad()这个函数

    于是gdb step 跟进

  发现这个函数导致main最后函数返回1

  即return 1

  我们是return 0才是正常退出

  所以一定是这个is_exit_status_bad()函数有问题了

 

 

   

   我们发现其源码中有几个常量,这些常量哪里来的?

   发现在这个源码中其引入了utils.h这个头文件

  

   

    这个头文件在nemu中有!

 

   

 

 

    可以发现其是通过枚举enum来定义

  关于enum的基础教程<-----

    反正我们是想要return 0,那么结合着我们是退出

   应该让nemu.state.state==NEMU_QUIT

    gdb print nemu.state.state 为1

    接下来知道咋做了吧!

 

就是这么简单

   

 

 

 

 

基础设施: 简易调试器

  为了实现下面的功能不得不看的函数

   字符串分割函数: strtok()<----

   字符串转换为数字的函数:atoi() <-----

    字符串转换为数字的函数更好的函数:sscanf()<-----

  

   

 

 

   打印寄存器

   

     既然他说了,那么我们就去isa_reg_display()函数下看看吧!

     

     发现他这里给出了寄存器数组

    我们不妨来看看他引入的头文件:

     

    发现这两个函数

      一个应该是从cpu中获得寄存器的值,一个应该是获得寄存器的名字

 

     还记得吗,寄存器是在哪里初始化的,这是谁做的?

     

    

   那我们就去isa-def.h下看看吧!

     

    确实发现了CPU中寄存器结构的描述

   同时也验证了上面gpr(idx)这个操作是取寄存器中的值

  

     word_t和vaddr_t是什么类型?

    不知道,现在还找不到

    

  然后让我们去cpu-exec.c看看吧!

    

   

     可以看到这个定义的全局变量

    

  在这文件下引入的头文件common.h中我们可以发现word_t 和 vaddr_t的定义:

    

     

 

   万事具备,让我们去实现isa_reg_display()函数吧!

   我们要打印什么?

   看看gdb咋打印的

   

   看起来是打印了寄存器的名字,其中的值

    好像第一个值是值的16进制,第二个值是值的10进制?

  话说:寄存器中保存的不就是立即数或者是地址吗?

 

show you code:  

/*@cilinmengye:implement the info r*/
void isa_reg_display() {
   ba int n = sizeof(regs)/sizeof(regs[0]);
    for (int i = 0 ; i < n ; i++ ) {
        printf ("%s\t 0x%x\t %u\n",reg_name(i,0),gpr(i),gpr(i));
    }
}

 小小的隐患:

  这里打印%u我不知道对不对(或者其他地方哪里错了)

  返回值是uint32_t 类型

  具体像 uint32_t , uint_64_t是什么看:这个<-----

 

 

扫描内存

 

gdb如何打印?<-----

不如先来看看我们的内存是如何定义的

 

   那我们去paddr.c下看看吧!

   

   发现我们这个数组是以uint8_t为单位的

   在init_mem()中发现使用pmem的一个案例

  

 

   我们再去paddr.h下看看是如何定义RESET_VECTOR的吧!

   

   

   这个CONFIG_MBASE是0x80000000,PMEM_LEFT以uint32_t即int的形式保存下来,

  这个CONFIG_PC_RESET_OFFSET是0x0,

  则RESET_VECTOR是0x80000000并且以uint32_t的形式保存下面

    

  在初始化内存后,因为要将客户程序装入到内存中,一定会有咋使用内存的案例!

   去看看!

  

   

   已经很明确了,我们要使用guest_to_host这个函数访问内存

  而且这个函数返回的是一个uint8_t*的指针

 

   在我们实现cmd_x下的sdb.c文件下,需要引入

   #include <memory/paddr.h>

  因为在这个头文件下定义了如下函数:

  

   

   然后再我们使用的时候编译器会知道我们这里需要使用guest_to_host()函数

    会为我们将实现了guest_to_host()函数的.c文件 链接起来

 

  然后我们遇到的问题是:

    在存放数据的时候,系统是按照大端模式还是小端模式?

       我们的内存数组是以uint8_t为单位的,我们如何按照讲义的要求将结果作为起始内存
    地址, 以十六进制形式输出连续的N个4字节?

    我们输入地址时是字符串的形式,如何将字符串转化为16进制下的数据?

    

  解决这些问题的最好方式是:

    从源代码中找到各个定义,并简化后尝试一下:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <string.h>
using namespace std;
#define CONFIG_MSIZE 0x8000000
static const uint32_t img[] = {
    0x800002b7, // lui t0,0x80000
    0x0002a023, // sw  zero,0(t0)
    0x0002a503, // lw  a0,0(t0)
    0x00100073, // ebreak (used as nemu_trap)
};
static uint8_t pmem[CONFIG_MSIZE] __attribute((aligned(4096)));
int main()
{
    memcpy(pmem, img, sizeof(img));
    uint32_t value = (pmem[3] << 24) |
                     (pmem[2] << 16) |
                     (pmem[1] << 8) |
                     pmem[0];
    for (int i=0;i<16;i++)
    {
        printf("%x ",pmem[i]);
        if ((i+1)%4==0)
            cout<<endl;
    }
    return 0;
}

  

  可以发现系统保存数据到我们的内存数组时是按照小端模式

   同时将我们可以通过位运算的方式将uint8_t得到一个uint32_t 连续4个字符的数据

   使用sscanf函数可以轻松将字符串转化为16进制下的数据

   sscanf使用方法<-----

  

 show you code 

  

//@cilinmengye:Implement x(print memory)
static int cmd_x(char *args){
    char *arg=strtok(NULL," ");
  if (arg==NULL){
    return inputIncomplete((char*)"x");
  }else{
    int n=atoi(arg);
    arg=strtok(NULL," ");
    //memory addree not given,default start from RESET_VECTOR
    paddr_t addr=RESET_VECTOR;
    if (arg!=NULL)
      sscanf (arg,"%x",&addr);
    for (int i=1;i<=n;i++){
      printf ("0x%x:\t0x%08x\n",addr,
      *guest_to_host(addr+3)<<24|*guest_to_host(addr+2)<<16|
      *guest_to_host(addr+1)<<8|*guest_to_host(addr));
      addr+=4;
    }
  }
    return 0;
}

 

 

 提升!开发效率

 还记得环境变量吗? 

  在我们的~/.bashrc中有上面两个环境变量

  其中NEMU_HOME十分好用

  因为我们在完成实验的使用总要到nemu下进行make run

  然而经常的cd 十分烦人!

  我们可以cd $NEMU_HOME直接到nemu

  然后cd - 可以快速回到原理的路径上

   

posted @ 2023-07-20 11:58  次林梦叶  阅读(633)  评论(0)    收藏  举报