Linux學習的點點滴滴(二)
其實本文跟Linux關係並不是那麼大,是我在自己寫CPU的過程中總結的東西,之前只用Word寫在自己電腦裏了,想着哪天放到博客上。
本文是在寫CPU進行測試的時候需要將彙編翻譯成機器碼的過程,剛開始學的時候,也遇到了一些小問題,在此記錄。
一、安裝GNU工具鏈
gcc編譯器使用的是龍芯公司的,使用了MIPS架構。下載地址
進入Linux虛擬機的/opt
文件夾(其實哪個都無所謂),在終端輸入tar -zxvf gcc-4.3-ls232.tar.gz
,這裏tar -zxvf
是.tar.gz
文件的解壓縮命令,後面會詳細介紹其他的解壓縮命令
解壓縮之後,首先配置環境變量,找到當前用戶的文件夾下的隱藏文件.bashrc
,如果遇到權限不夠,用chmod
修改權限之後,打開文件,在文件最後一行加上
export PATH=”$PATH:password/opt/gcc-4.3-ls232/bin”
在終端輸入echo $PATH
查看已經配好的環境變量,(echo是回顯命令,是常見的腳本命令)
然後輸入mipsel-linux-gcc -v
,如下圖表示GNU工具鏈安裝完成:
有個很坑的地方,如果提示:bash ./ 沒有那個文件或目錄,是因爲Ubuntu是64位的,沒有32位的運行庫,安裝一個32位運行庫即可:apt-get install lib32z1
至此基本的準備工作就完成了,但是在此過程中不熟悉Linux系統會導致一些列問題,在此總結一下。
二、解壓縮命令
tar
-c: 建立壓縮檔案
-x:解壓
-t:查看內容
-r:向壓縮歸檔文件末尾追加文件
-u:更新原壓縮包中的文件
這五個是獨立的命令,壓縮解壓都要用到其中一個,可以和別的命令連用但只能用其中一個。下面的參數是根據需要在壓縮或解壓檔案時可選的。
-z:有gzip屬性的
-j:有bz2屬性的
-Z:有compress屬性的
-v:顯示所有過程
-O:將文件解開到標準輸出
下面的參數-f是必須的
-f: 使用檔案名字,切記,這個參數是最後一個參數,後面只能接檔案名。
tar -cf all.tar *.jpg
這條命令是將所有.jpg的文件打成一個名爲all.tar的包。-c是表示產生新的包,-f指定包的文件名。
tar -rf all.tar *.gif
這條命令是將所有.gif的文件增加到all.tar的包裏面去。-r是表示增加文件的意思。
tar -uf all.tar logo.gif
這條命令是更新原來tar包all.tar中logo.gif文件,-u是表示更新文件的意思。
tar -tf all.tar
這條命令是列出all.tar包中所有文件,-t是列出文件的意思
tar -xf all.tar
這條命令是解出all.tar包中所有文件,-t是解開的意思
//壓縮
tar -cvf jpg.tar *.jpg //將目錄裏所有jpg文件打包成tar.jpg
tar -czf jpg.tar.gz *.jpg //將目錄裏所有jpg文件打包成jpg.tar後,並且將其用gzip壓縮,生成一個gzip壓縮過的包,命名爲jpg.tar.gz
tar -cjf jpg.tar.bz2 *.jpg //將目錄裏所有jpg文件打包成jpg.tar後,並且將其用bzip2壓縮,生成一個bzip2壓縮過的包,命名爲jpg.tar.bz2
tar -cZf jpg.tar.Z *.jpg //將目錄裏所有jpg文件打包成jpg.tar後,並且將其用compress壓縮,生成一個umcompress壓縮過的包,命名爲jpg.tar.Z
rar a jpg.rar *.jpg //rar格式的壓縮,需要先下載rar for linux
zip jpg.zip *.jpg //zip格式的壓縮,需要先下載zip for linux
//解壓
tar -xvf file.tar //解壓 tar包
tar -xzvf file.tar.gz //解壓tar.gz
tar -xjvf file.tar.bz2 //解壓 tar.bz2
tar -xZvf file.tar.Z //解壓tar.Z
unrar e file.rar //解壓rar
unzip file.zip //解壓zip
//總結
1、*.tar 用 tar -xvf 解壓
2、*.gz 用 gzip -d 或者gunzip 解壓
3、*.tar.gz 和 *.tgz 用 tar -xzf 解壓
4、*.bz2 用 bzip2 -d或者用bunzip2 解壓
5、*.tar.bz2 用 tar -xjf 解壓
6、*.Z 用 uncompress 解壓
7、*.tar.Z 用tar -xZf 解壓
8、*.rar 用 unrar e解壓
9、*.zip 用 unzip 解壓*
三、關於環境變量
類似於Windows下的環境變量,本質含義就是讓系統運行一個程序時,不僅僅要在當前目錄下面尋找,還要到PATH指定的路徑下尋找,此操作實際簡化了程序執行的複雜度,提高了效率。
以上面的GCC路徑爲例,我們寫入的PATH路徑爲:export PATH=”$PATH:/opt/gcc-4.3-ls232/bin”
什麼意思?可以把PATH看成一個字符串,用“/”、“:”、“\$”
等符號進行劃分,系統根據劃分的字符串尋找目標地址。因爲PATH是一個字符串,用“$PATH”表示引用這個字符串,就是表示PATH所指的內容;:
是Linux中分隔符,相當於Windows中的;
,因此該語句表示在原有的PATH路徑下再加上/opt/gcc-4.3-ls232/bin
路徑,我們在終端中檢驗一下:
實際上,若要僅僅配置GCC,不需要加上之前的PATH路徑,因此只要這樣寫即可:
export PATH=”/opt/gcc-4.3-ls232/bin"
.我們在終端中再次輸入echo $PATH,結果證明是正確的,gcc安裝無誤:
四、編譯彙編指令
我們先編寫一組彙編指令,命名爲inst_rom.S
然後我們在終端中輸入指令:
mipsel-linux-as -mips32 inst_rom.S -o inst_rom.o
,表示用as工具將inst_rom.S文件編譯成inst_rom.o
文件再輸入:
mipsel-linux-ld -T ram.ld inst_rom.o -o inst_ram.om
,表示將inst_rom.o
文件鏈接成inst_rom.om
文件。再輸入:
mipsel-linux-objcopy -O binary inst_rom.om inst_ram.bin
,根據.om文件得到了bin文件,再輸入:
mipsel-linux-objdump -D inst_rom.om > inst_ram.asm
,對彙編指令進行反彙編,得到與機器指令對應的二進制字。
最後將Bin2MEM.exe拷貝到與inst_rom系列文件相同的目錄下,執行
./Bin2Mem.exe -f inst_rom.bin -o inst_rom.data
第一次執行應該需要修改Bin2Mem.exe文件的權限,輸入:
Chmod 777 Bin2Mem.exe
,最終轉化爲與Vivado程序中讀入的文件類型(.data),執行結果如圖:
生成的文件:
我們打開inst_rom.data文件查看:
這便是我們一開始那幾條彙編指令的機器指令。
五、大小端地址
大端(存儲)模式:是指一個數據的低位字節序的內容放在高地址處,高位字節序存的內容放在低地址處。
小端(存儲)模式:是指一個數據的低位字節序內容存放在低地址處,高位字節序的內容存放在高地址處。(可以總結爲“小小小”即低位、低地址、小端)
在上述的例子中,我們發現原來的機器指令34011100變成了00110134,高位數據放在了地址的高位,是小端存儲,這樣看上去不是很方便,我們可以修改編譯指令改變最終編譯的結果,輸入mipsel-linux-as -help
查看幫助:
我們修改指令:
查看inst_rom.data文件:
已經修改爲大端模式了。我們需要修改爲大端模式,因爲該OpenMIPS就是大端模式。
六、Make工具
每次都輸入四個(加上.asm一共五個)指令顯得很麻煩,我們可以編寫一個腳本來自動執行命令。
make便是Linux上的一個腳本,當我們只輸入make命令的工作流程是:
- make會在當前目錄下找名字叫“Makefile”或“makefile”的文件;
- 如果找到,它會找文件中的第一個目標文件(target),在上面的例子中,他會找到“inst_rom.data”這個文件,並把這個文件作爲最終的目標文件;
- 如果inst_rom.data文件不存在,或是.data所依賴的後面的 .om 文件的文件修改時間要比.data這個文件新,那麼make會執行下面定義的命令來生成.data文件;
- 如果.data所依賴的.om文件也存在,那麼make會在當前文件中找目標爲.om文件的依賴性,如果找到再根據命令生成.om文件(這是一個遞歸的過程);
如果在找尋的過程中,出現了被依賴的文件找不到的錯誤,那麼make就會直接退出,並報錯。
如果在一條依賴鏈中,比如:A依賴B,B依賴C,C依賴D。那麼當D更新後,make發現D比C新則會重新構建C,以此類推,最終A也會被更新。
簡單的說,makefile帶來的好處就是——自動化編譯,只要一個make命令,所有工程和文件自動編譯,類似於Shell的.sh和cmd的.bat
我們編寫如下代碼:
ifndef CROSS_COMPILE
CROSS_COMPILE = mipsel-linux-
endif
CC = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
OBJECTS = inst_rom.o
export CROSS_COMPILE
all:inst_rom.data inst_rom.om inst_rom.o inst_rom.bin inst_rom.asm
%.o:%.S
$(CC) -mips32 -EB $< -o $@
inst_rom.om:ram.ld $(OBJECTS)
$(LD) -EB -T ram.ld $(OBJECTS) -o $@
inst_rom.bin:inst_rom.om
$(OBJCOPY) -O binary $< $@
inst_rom.asm:inst_rom.om
$(OBJDUMP) -D $< >$@
inst_rom.data:inst_rom.bin
./Bin2Mem.exe -f $< -o $@
clean:
rm -f *.o *.om *.bin *.data
其中 $<
表示第一個依賴文件的名稱,$@
表示目標的完整名稱
至此,我們用gcc編譯彙編指令生成機器碼的過程就做完了。
七、ram.ld鏈接代碼
MEMORY
{
ram(RW) : ORIGIN = 0x00000000, LENGTH = 0x00001000
}
SECTIONS
{
/*
For some reason the linker script can't see the _reset_vector symbol
(even if we declare it global), so we explicitly set it. */
.text :
{
*(.text)
} > ram
.data :
{
*(.data)
} > ram
.bss :
{
*(.bss)
} > ram
.stack ALIGN(0x10) (NOLOAD):
{
*(.stack)
_ram_end = .;
} > ram
}
ENTRY (_start)
八、Bin2Mem.c代碼
#include <stdlib.h>
#include <stdio.h>
char *option_invalid = NULL;
char *option_file_in = NULL;
char *option_file_out = NULL;
FILE *file_in_descriptor = NULL;
FILE *file_out_descriptor = NULL;
void exception_handler(int code) {
switch (code) {
case 0:
break;
case 10001:
printf("Error (10001): No option recognized.\n");
printf("Please specify at least one valid option.\n");
break;
case 10002:
printf("Error (10002): Invalid option: %s\n", option_invalid);
break;
case 10003:
printf("Error (10003): No input Binary file specified.\n");
break;
case 10004:
printf("Error (10004): Cannot open file: %s\n", option_file_in);
break;
case 10005:
printf("Error (10005): Cannot create file: %s\n", option_file_out);
break;
default:
break;
}
if (file_in_descriptor != NULL) {
fclose(file_in_descriptor);
}
if (file_out_descriptor != NULL) {
fclose(file_out_descriptor);
}
exit(0);
}
int main(int argc, char **argv) {
int i=0,j=0;
unsigned char temp1,temp2,temp3,temp4;
unsigned int option_flag = 0;
while (argc > 0) {
if (**argv == '-') {
(*argv) ++;
switch (**argv) {
case 'f':
option_flag |= 0x4;
argv ++;
option_file_in = *argv;
argc --;
break;
case 'o':
option_flag |= 0x8;
argv ++;
option_file_out = *argv;
argc --;
break;
default:
option_flag |= 0x1;
(*argv) --;
option_invalid = *argv;
break;
}
}
argv ++;
argc --;
}
file_in_descriptor = fopen(option_file_in, "rb");
if (file_in_descriptor == NULL) {
exception_handler(10004);
}
file_out_descriptor = fopen(option_file_out, "w");
if (file_out_descriptor == NULL) {
exception_handler(10005);
}
while (!feof(file_in_descriptor)) {
fscanf(file_in_descriptor, "%c", &temp1);
fscanf(file_in_descriptor, "%c", &temp2);
fscanf(file_in_descriptor, "%c", &temp3);
fscanf(file_in_descriptor, "%c", &temp4);
if(!feof(file_in_descriptor)) {
fprintf(file_out_descriptor, "%02x", temp1);
fprintf(file_out_descriptor, "%02x", temp2);
fprintf(file_out_descriptor, "%02x", temp3);
fprintf(file_out_descriptor, "%02x", temp4);
fprintf(file_out_descriptor, "\n");
}
}
exception_handler(0);
return 0;
}