首页 > 代码库 > 自己动手写CPU之第四阶段(3)——MIPS编译环境的建立

自己动手写CPU之第四阶段(3)——MIPS编译环境的建立

将陆续上传本人写的新书《自己动手写CPU》(尚未出版)。今天是第13篇。我尽量每周四篇

4.4 MIPS编译环境的建立

      OpenMIPS处理器在设计的时候就计划与MIPS32指令集架构兼容,所以能够使用MIPS32架构下已有的GNU开发工具链。本节将说明怎样安装使用GNU开发工具链以及怎样制作Makefile文件。从而以更加方便、快捷、自己主动的方式对測试程序进行编译。并得到指令存储器ROM的初始化文件inst_rom.data。

4.4.1 VisualBox的安装与设置

      GNU工具链要安装在Linux环境下,大多数读者使用的可能都是Windows平台。能够首先安装Linux虚拟机,再在Linux虚拟机中安装GNU工具链。笔者推荐使用OpenCores网站上提供的一个Linux虚拟机镜像。该虚拟机预装的是Ubuntu系统。

      在浏览器中输入地址:ftp://openrisc.opencores.org/virtualbox-image/。FTP的username和password都是openrisc,登录后会出现如图4-14所看到的界面。

技术分享

      下载最新的那个文件就能够了。笔者使用的是2011-12-15版。下载完毕后解压该文件。大约4GB左右。

此时还须要下载VisualBox才干够打开该文件。VisualBox是一款开源的虚拟机软件,本书使用的是4.1.22版。下载完毕后安装VisualBox。安装完毕后打开VisualBox,界面如图4-15所看到的。

技术分享

      点击“新建”出现“新建虚拟机”向导,点击“下一步”。出现如图4-16所看到的界面。

 技术分享

      此处操作系统选择Linux,版本号选择Ubuntu,点击下一步。设置内存大小。如图4-17所看到的。

技术分享

      内存大小根据计算机情况设置,本人设置的是512M,已经够用了,毕竟我们须要编译的程序都是十分简单的,点击下一步。选择“使用现有的虚拟硬盘”。然后选择解压后的虚拟机文件。如图4-18所看到的。

技术分享

      点击“下一步”,VisualBox会将用户刚才的设置都列出来,确认无误后,点击“创建”。这样虚拟机就创建好了。启动虚拟机。显示如图4-19所看到的。

技术分享

      至此Linux虚拟机就已经安装好了。还须要多做一步工作,就是设置虚拟机与Windows宿主机之间的共享,这样方便以后在两个系统之间传递文件。先关闭Ubuntu虚拟机,然后打开VisualBox中虚拟机的设置界面,选择“共享目录”,如图4-20所看到的。

技术分享

      点击界面右边的加入目录button。出现如图4-21所看到的界面:

技术分享

      在当中选择共享目录的路径。设置名称。參考图4-21所看到的设置。设置完毕后,能够启动虚拟机,打开终端,输入命令:

sudo mount –t vboxsf UbuntuShareFolder /mnt/

      该命令的作用是将共享目录挂载在/mnt/目录下。sudo表示以Root用户身份运行该命令,终端会提示输入password。Ubuntu虚拟机默认Root用户的password是openrisc。这样就实现了虚拟机与宿主机的文件共享。对虚拟机而言共享文件放在/mnt/路径下,对宿主机而言共享文件放在图4-21所看到的的F盘UbuntuShareFolder目录下。

4.4.2 GNU工具链的安装

      在本书附带光盘的tools文件夹下提供了GNU工具链安装文件,文件名称是mips-sde-elf-i686-pc-linux-gnu.tar.tar,将该文件拷贝到上一小节设置的共享文件夹下。就可以通过Ubuntu虚拟机訪问该文件。

将安装文件拷贝到Ubuntu的/opt文件夹下。打开Ubuntu的终端,使用例如以下命令解压缩:

cd /opt
tar vfxj mips-sde-elf-i686-pc-linux-gnu.tar.tar

      然后打开用户主文件夹Home文件夹。在窗体菜单条中选择View->Show Hidden Files,以显示全部文件,这样能够找到一个隐藏文件.bashrc。在此文件的最后增加 PATH 的设置。例如以下。

export PATH=”$PATH:/opt/mips-4.3/bin”

      又一次启动Ubuntu系统。

      重新启动后。打开终端。在当中输入mips-sde-elf-,然后按两次Tab键,会列出刚刚安装的,针对MIPS平台的全部编译工具,如图4-22所看到的。表示GNU工具链成功安装。

技术分享

      GNU工具链包括非常多工具。但我们须要使用的不多,基本的几个工具例如以下。此处使用的是通用名,针对MIPS平台的工具,会在名称前添加“mips-sde-elf-”前缀。

  •  as:GNU汇编器。通常也称为GAS (GNU Assembler)。as对汇编源程序进行编译产生目标文件。
  •  ld:GNU链接器。as产生的目标文件须要由ld进行链接、重定位数据产生可运行文件。

  •  objcopy:用于将一种格式的目标文件复制成第二种格式。
  •  objdump:用于列出关于二进制文件的各种信息。
  •  readelf:类似于objdump,可是它仅仅能处理ELF格式的文件。

4.4.3 使用GNU工具进行编译

      本小节就使用GNU工具编译4.3节的測试程序。首先在Ubuntu虚拟机中新建一个文件。文件名称为inst_rom.S,内容例如以下。

相比4.3节的測试程序,多了三条编译指导语句。

.org 0x0          // 指示程序从地址0x0開始
.global _start    // 定义一个全局符号_start
.set noat         // 同意自由使用寄存器$1 
_start:
   ori $1,$0,0x1100        # $1 = $0 | 0x1100 = 0x1100
   ori $2,$0,0x0020        # $2 = $0 | 0x0020 = 0x0020
   ori $3,$0,0xff00        # $3 = $0 | 0xff00 = 0xff00
   ori $4,$0,0xffff        # $4 = $0 | 0xffff = 0xffff

      对“set noat”做进一步说明,这是一个汇编控制伪操作。

在第1章介绍MIPS32架构中的通用寄存器时已经提到MIPS32中的通用寄存器都有约定名称,其使用方法也遵循一些约定,比方:寄存器$1。编程时的约定名称为at,一般留给汇编器使用,程序中不直接使用。假设直接使用。汇编器会发出警告。此处设置“set noat”就是表示能够自由使用寄存器$1。汇编器不会发出警告。

      在Ubuntu中打开终端。使用cd命令将路径调整到上述inst_rom.S所在文件夹。然后使用例如以下命令编译代码。

当中加入了“-mips32”选项,表示依照MIPS32指令集架构进行编译。

mips-sde-elf-as –mips32 inst_rom.S –o inst_rom.o

      上述命令会得到目标代码inst_rom.o。打开inst_rom.o文件,能够发现其最初的四个字节是:0x7F、0x45、0x4C、0x46,这说明inst_rom.o是一个ELF文件。

技术分享

      为了便于读者理解。以下将简介一下ELF文件,读者朋友假设对这不感兴趣或者希望尽快了解编译链接过程的能够跳过以下的介绍。直接阅读4.4.4节。

      ELF(Executable and LinkableFormat)可运行链接格式。是UNIX系统实验室(USL)作为应用程序二进制接口(ABI:Application Binary Interface)而开发和公布的。ELF目标文件有三种类型。

      (1)可重定位(Relocatable)文件:保存着代码和适当的数据,用来和其它Object文件一起创建一个可运行文件或共享文件。

      (2)可运行(Executable)文件:保存着一个用来运行的程序,该文件指出了怎样来创建程序进程映象。

      (3)共享目标文件:包括了在两种使用环境中链接的代码和数据。首先链接器(ld)能够将它和其余可重定位文件和共享目标文件一起处理。生成另外一个目标文件(比方:编译器和链接器把*.o和*.so一起装配成一个*.exe文件)。

其次,动态链接器(Dynamic Linker)可将它与某个可运行文件以及其他共享目标文件组合在一起创建进程映像(比方:动态载入器把*.exe程序和*.so载入进内存运行)。

      不管何种类型的ELF文件,其结构都是同样的。

ELF文件由四部分组成:ELF header、Program header table、Sections、Section header table。其最開始的部分就是ELF header。定义例如以下:

#define EI_NIDENT 16
typedef struct{
       unsigned char     e_ident[EI_NIDENT]; //占用16个字节
       Elf32_Half        e_type;       //Elf32_Half表示是2个字节大小
       Elf32_Half        e_machine;
       Elf32_word        e_version;    //Elf32_Word表示是4个字节大小
       Elf32_Addr        e_entry;      //Elf32_addr也表示4个字节大小
       Elf32_Off         e_phoff;      //Elf32_Off也表示4个字节大小
       Elf32_Off         e_shoff;
       Elf32_Word        e_flags;
       Elf32_Half        e_ehsize;
       Elf32_Half        e_phentsize;
       Elf32_Half        e_phnum;
       Elf32_Half        e_shentsize;
       Elf32_Half        e_shnum;
       Elf32_Half        e_shstrndx;
}Elf32_Ehdr;

      開始四个字节是固定不变的:0x7F,紧接着是ELF三个字符的ASCII码,这四个字节表明这个文件是一个ELF文件。此处以inst_rom.o为例。介绍e_ident字段后面字节的含义。參考图4-23。

  •  e_type是01,表示是可重定位文件
  •  e_machine表示执行该程序须要的体系结构。此处为0x08,表示MIPS R3000
  •  e_version表示文件版本号,此处是1
  •  e_entry表示程序的入口地址,此处是0x0
  •  e_phoff是Program header table在文件里的偏移量(以字节计数),此处是0x0
  •  e_shoff是Section header table在文件里的偏移量(以字节计数),此处为0x98
  •  e_flags为保存着相关文件的特定处理器信息,此处为0x50000000,表示MIPS32
  •  e_ehsize表示ELF header的大小。此处为0x34
  •  e_phentsize表示Program header table中每个条目(一个Program header)的大小,此处为0x0
  •  e_phnum表示Program header table中有多少个条目,此处为0
  •  e_shensize表示Section header table中每个条目(一个Section header)的大小,此处为0x28
  •  e_shnum表示Section header table中有多少个条目,此处为0x09
  •  e_shstrndx保存着字符表相关入口的节区头部表索引。此处为0x06

     通过上述解释能够了解到这个文件是一个可重定位(Relocatable)文件,不是可运行文件,同一时候了解到该文件包括的Program header table、Section header table信息。

对inst_rom.o而言,没有Program header table。依照给出的偏移信息,我们能够得到Section header table表的位置。通过Section header table得到每一个Section的位置。

      当然。依照ELF header的内容以及Section header table,我们能够按图索骥地分析全部Section,可是这样效率太慢,借助于GNU工具链中的mips-sde-elf-readelf。我们能够直接得到Section信息,如图4-24所看到的。

技术分享

      注意加入“-S”选项。这里列出了9个Section的信息,注意当中的“.text”这个Section,它的起始地址是0x34。长度是0x10。我们列出这个Section的内容如图4-25所看到的。

技术分享

      參考4.3.3节可知,这0x10个字节正是測试程序中的4条指令相应的二进制字。

4.4.4 使用GNU工具进行链接

      通过编译得到了一个可重定位ELF文件。但这个文件还不能运行。须要通过链接转化为可运行文件,然后才干运行。使用链接工具mips-sde-elf-ld完毕这项工作,在mips-sde-elf-ld的參数中须要声明一个链接描写叙述脚本。链接描写叙述脚本描写叙述了输入文件的各个Section怎样映射到输出文件的各个Section中。并控制输出文件里Section和符号的内存布局。能够通过新建一个Document作为链接描写叙述脚本。文件名称为ram.ld,内容例如以下。


MEMORY
        {       
        ram    : ORIGIN = 0x00000000, LENGTH = 0x00001000
        }

SECTIONS
{
	.text :
        {
        *(.text)
        } > ram

        .data :
        {
        *(.data)
        } > ram

        .bss :
        {
        *(.bss)
        } > ram
}

Entry(_start)

      当中定义了一个存储块——ram。其起始地址是0x0。长度是0x1000。然后指示链接器输出文件包括三个Section,各自是.text、.data、.bss。当中.text从ram的起始地址開始存放,后面跟着.data、.bss,而且输入文件的Section .text存放在输出文件的.text中,输入文件的Section .data存放在输出文件的.data中,输入文件的Section .bss存放在输出文件的.bss中。最后的Entry指定程序的入口地址,也就是第一条运行的指令地址是_start符号的值,从汇编代码中可知_start符号就是0x0。如今就能够使用链接器了,在Ubuntu虚拟机的终端中输入例如以下命令。

mips-sde-elf-ld –T ram.ld inst_rom.o –o inst_rom.om

      得到链接后的文件inst_rom.om。这也是一个ELF格式的文件,其ELF header如图4-26所看到的。

技术分享

      上一小节是手工分析inst_rom.o的ELF header,主要是为了帮助读者理解,事实上能够直接使用工具分析ELF header,在终端中输入例如以下命令将自己主动分析inst_rom.om的ELF header。


mips-sde-elf-readelf –h inst_rom.om
      当中加上參数“-h”表示仅仅读取ELF header,得到结果如图4-27所看到的。
技术分享

      从中可知inst_rom.om是一个可运行文件。

读者朋友或许已经注意到了。inst_rom.om比inst_rom.o多了Program header,而这在inst_rom.o里面是没有的。与Section header一样,Program header也能够使用一个结构体描写叙述。例如以下。

typedef struct{
       Elf32_Word          p_type;
       Elf32_Off           p_offset;
       Elf32_Addr          p_vaddr;
       Elf32_Addr          p_paddr;
       Elf32_Word          p_filez;
       Elf32_Word          p_memsz;
       Elf32_Word          p_flags;
       Elf32_Word          p_align;
}Elf32_Phdr;
       我们还是使用工具mips-sde-elf-readelf从inst_rom.om中分析出一个Program header,然后结合这个Program header解释上面各个各项的含义。

使用例如以下命令得到Program header的信息。

mips-sde-elf-readelf –l inst_rom.om
      当中加上“-l”參数。表示列出Program header的信息,显示如图4-28所看到的。
技术分享

      借助上图介绍Program header各个字段的含义:

  •  p_type为LOAD,表示可载入
  •  p_offset表示段的第一个字节在文件inst_rom.om中的偏移,此处为0x10000
  •  p_vaddr表示段的第一个字节在内存中地址,此处为0
  •  p_paddr为0。在物理地址定位有关联的系统中,该成员是为该段的物理地址而保留的
  •  p_filez表示段在文件里的长度。此处为0x10
  •  p_memsz表示段在内存中的长度。此处为0x10
  •  p_flags为RE,表示可读、可运行
  •  p_align为0x10000,依据此项确定段在文件以及内存中怎样对齐

      该Program header表示将inst_rom.om文件里从偏移0x10000開始的0x10个字节放置在内存的0x0处,打开inst_rom.om能够发现从偏移0x10000開始的0x10个字节的内容与inst_rom.o中Section .text的内容一样,所以当这个Program Section载入入内存后,会使得内存从地址0x0開始的0x10个字节存放的就是測试程序的4条指令。

      分析到这里,读者是不是对编译、链接过程有了比之前更深的了解?事实上这些背景知识与OpenMIPS处理器关系不大,可是知道这些有助于理解编译链接的过程。总结一下。编译链接的过程非常easy。仅仅须要两步,例如以下。

编译:mips-sde-elf-as –mips32 inst_rom.S –o inst_rom.o
链接:mips-sde-elf-ld –T ram.ld inst_rom.o –o inst_rom.om

4.4.5 得到ROM初始化文件

      上一小节得到的inst_rom.om是一个ELF格式的可运行文件,与我们希望的指令存储器ROM初始化文件inst_rom.data的格式有非常大差别,须要进行格式转化。

在GNU工具链中提供了还有一个工具mips-sde-elf-objcopy,用于将一种格式的目标文件转化成第二种格式。

在这里,能够使用mips-sde-elf-objcopy得到inst_rom.om的二进制(Binary)形式,用法例如以下。得到的二进制文件inst_rom.bin的内容如图4-29所看到的。

mips-sde-elf-objcopy –O binary inst_rom.om inst_rom.bin
技术分享

      从图4-29能够发现。bin文件的内容正是測试程序中4条指令相应的二进制字。如今仅仅须要编写一个小程序将bin文件转化为ModelSim中存储器初始化文件的格式。这个小程序非常easy。此处不再列出代码。在本书附带的光盘中能够找到源程序。程序名为Bin2Mem.exe,用法例如以下。得到的inst_rom.data文件如图4-30所看到的。

./Bin2Mem.exe –f inst_rom.bin –o inst_rom.data
技术分享

      好了,如今回顾一下从源码得到ModelSim仿真时能够使用的指令存储器ROM初始化文件一共须要4步:编译、链接、得到bin文件、格式转化。例如以下。

编译:         mips-sde-elf-as –mips32 inst_rom.S –o inst_rom.o
链接:         mips-sde-elf-ld –T ram.ld inst_rom.o –o inst_rom.om
得到bin文件:  mips-sde-elf-objcopy –O binary inst_rom.om inst_rom.bin
格式转化:      ./Bin2Mem.exe –f inst_rom.bin –o inst_rom.data

未完待续。兴许将上传第4阶段的代码!



自己动手写CPU之第四阶段(3)——MIPS编译环境的建立