前兩天猴子先偷偷把之前編完的執行檔拿來驗證,
今天來把 Cross Compiler 的部分補完。
有些夥伴沒接觸過嵌入式系統,
可能不了解什麼是 Cross Compile,
初學者在寫程式的時候,
通常都是編譯環境和執行環境在同一個平台上。
例如:在自己電腦(x86)上寫 C 語言程式、編譯、執行,
就算從別台電腦把程式拿來執行,也同樣是在 x86 的電腦上面。
但是學過底層運作方式的夥伴就會知道,
實際上不同的電腦支援的指令是不一樣的,
就算同樣是 x86 ,也會因為型號的不同而不同(ex: sse 指令集),
導致 A 電腦編譯出來的執行檔在 B 電腦上面無法執行。
簡單來說就是當開發環境(寫程式、編譯的電腦)和執行環境(執行的電腦)不一樣的時候,
就需要輸出執行環境能運作的執行檔,才能放到要執行的平台上執行,
也就是剛剛提到的 Cross Compile。
最常見的就是在 x86 (桌上型電腦)上面開發 ARM (手機)平台上的軟體,
像 Linux 在桌上型電腦上可能 2 個小時就可以編譯完,
在 Raspberry Pi 上面大概按下編譯就可以睡到隔天,
光編譯的時間差就會導致開發效率的低落,
更別說 x86 平台上豐富的軟體支援了。
說了那麼多,
Cross Compile 的流程除了執行環境以外,其實跟一般開發流程一模一樣,
關鍵的地方在於如何取得 Cross Compiler,
下面就是怎麼取得在 x86 上面運作,輸出 RISC-V 執行檔的 Cross Compiler 流程。
$git clone https://github.com/riscv/riscv-gnu-toolchain
$cd riscv-gnu-toolchain && git submodule update -i -r
$sudo apt-get install autoconf automake autotools-dev curl python3 libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev
rv32i
以及 ilp32
(32-bit soft-float) 的 abi,/home/user/Desktop/code/rv32i-ilp32-gnu-toolchain
//in riscv-gnu-toolchain folder
$./configure --prefix=/home/user/Desktop/code/rv32i-ilp32-gnu-toolchain --with-arch=rv32i --with-abi=ilp32
$make
//wait for loooooooooooong time
Makefile
中指定 Toolchain 路徑//console
$make test
//Makefile
...
export PATH=$PATH:/home/user/Desktop/code/rv32i-ilp32-gnu-toolchain/bin
CC= riscv32-unknown-elf-gcc
test: test.c
$(CC) $< -o test
...
github 頁面 Tag: ITDay29
昨晚誤會 gcc 編譯出來的執行檔有 Memory Access Misalignment,
還以為自己對環境的運作了解的不夠深入,
講了大半天的 LOAD/STORE Exception 規範,該不會全錯?!
冷靜下來追查才發現是誤會一場,猴子嚇猴子果然是最可怕的!
Memory Access Misalignment 的問題是因為參考 RISC-V-TLM 的輸出,
回推 x2
初始值為 0x3FFFFdF + 32
= 0x3FFFFFF
,
把 stack 位置設定為 0x3FFFFFF
導致的問題,改成 0x40000
就OK了!
//RISC-V-TLM output
time 0 s: PC: 0x10054. time 0 s: ADDI: x2 + -32 -> x2(0x3ffffdf)
//cpu.cpp
register_file->set_value_integer(REGISTER_INTERFACE::x2, 0x40000);
因為 gcc 編譯出來的是 ELF 格式,目前 Simulator 還不支援,
我們支援的 Intel HEX 格式需要用 objcopy 工具,
剛剛編 Toolchain 的時候也有一起做出來。
//Makefile
CROSS_COMPILE ?= riscv32-unknown-elf-
CC= $(CROSS_COMPILE)gcc
CFLAGS = -Wall -I. -O0 -nostdlib -march=rv32i -mabi=ilp32 --entry main
SRC= test
TARGET= binary
export PATH:=/home/user/Desktop/code/rv32i-ilp32-gnu-toolchain/bin:${PATH}
all: $(addsuffix .o, $(SRC))
$(CROSS_COMPILE)objcopy -Oihex $< $(TARGET).hex
readelf -a $<
%.o: %.c
$(CC) $(CFLAGS) $< -o $@
clean:
-rm *.o *.hex
咦?!
這次可以從 readelf 印出來的資訊找到 main
起始位置為 0x10054
,
該不會我誤會 RISC-V-TLM 了吧!
明天再來研究看看 Intel HEX Format 的 segmentation 是不是真的要 * 16
。
//RISCV-SIM/test
$make
...
Symbol table '.symtab' contains 13 entries:
...
9: 00010054 32 FUNC GLOBAL DEFAULT 1 main
...
上面的疑問先放一邊,
今天和猴子一起來享受執行第一個程式的成就感吧,
一樣從 Cpu 設定 Simulator 起始執行位置為 0x1054
,
再把剛剛編出來的 binary.hex 放到 simulator 旁邊執行!
//cpu.cpp
...
register_file->set_pc(0x1054);
register_file->set_value_integer(REGISTER_INTERFACE::x2, 0x40000);
while(true) {
step();
wait(delay);
}
...
//RISCV-SIM
$make run
...
current_pc: 0x1054 target_pc: 0x1058 ADDI 2 16 2 rs1Value: 0x3fff0 rs2Value: 0x0 rdValue: 0x3fff0 immValue: 0xfffffff0
current_pc: 0x1058 target_pc: 0x105c SW 2 8 12 rs1Value: 0x3fff0 rs2Value: 0x0 rdValue: 0x0 immValue: 0xc
current_pc: 0x105c target_pc: 0x1060 ADDI 2 16 8 rs1Value: 0x3fff0 rs2Value: 0x0 rdValue: 0x40000 immValue: 0x10
current_pc: 0x1060 target_pc: 0x1064 ADDI 0 0 15 rs1Value: 0x0 rs2Value: 0x0 rdValue: 0x0 immValue: 0x0
current_pc: 0x1064 target_pc: 0x1068 ADDI 15 0 10 rs1Value: 0x0 rs2Value: 0x0 rdValue: 0x0 immValue: 0x0
current_pc: 0x1068 target_pc: 0x106c LW 2 12 8 rs1Value: 0x3fff0 rs2Value: 0x0 rdValue: 0x0 immValue: 0xc
current_pc: 0x106c target_pc: 0x1070 ADDI 2 16 2 rs1Value: 0x40000 rs2Value: 0x0 rdValue: 0x40000 immValue: 0x10
current_pc: 0x1070 target_pc: 0x0 JALR 1 0 0 rs1Value: 0x0 rs2Value: 0x0 rdValue: 0x0 immValue: 0x0
INVALID: Opcode :0
Illegal Instruction, end simulation!
exception!
成功!
自己編譯 Cross Compiler 就是這麼簡單~
恭喜即將邁入完賽啦!
謝謝!
雖然這陣子真的快死掉了,
但真的要結束心情還是有點複雜XD
很高興看到你的留言,
這次完賽之後還會繼續完善這個專案,之後也歡迎多多交流~