自己動手寫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階段的代碼!




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM