Hello World: 编译环境建立

前两天猴子先偷偷把之前编完的执行档拿来验证,
今天来把 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 流程。

编译 RISC-V Toolchain 流程

  1. riscv-gnu-toolchain 下载原始码到自己喜欢的位置
    记得准备足够的硬碟空间,编译後总大小约 12 GB
$git clone https://github.com/riscv/riscv-gnu-toolchain
$cd riscv-gnu-toolchain && git submodule update -i -r
  1. 安装必要工具,猴子用的 Linux 环境是由 Jserv 和 PCMan 两位前辈发起的 Lubuntu,
    按照 Ubuntu 的指示做就可以了
$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
  1. 设定 Toolchain,因为目前只有实作 I 指令集,
    这边使用 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
  1. 编译并安装到刚刚指定的资料夹,
    请注意记忆体大小和电脑速度一定要够,
    不然编不过又找不到答案 + 等到天荒地老
$make
//wait for loooooooooooong time
  1. 执行,猴子习惯在 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 就是这麽简单~


<<:  [day28]优化架构-订单留存及检核(1)

>>:  Day28 NodeJS实作 II

学习日记-1

最近几天在网站上看了有管git的资料, 发现了 "连猴子都能懂的Git指南" 就...

【设计+切版30天实作】|Day20 - Navigation bar - 打破预设的navbar排版

大纲 昨天完成了header的下半部分,今天的任务就要来完成header的上半部分 —— Navig...

Day 04 - 多维度的PM职能(布局蓝图)

图片来源 延续昨日的结论, 从整体的目标设定结果来看, 其实可以明显分成三个类型, 即(1)技术面...

Day 22 Context

第 22 天 ! 当我们资料项下传递的时候, 会发现, component 的阶层越深, 传递资讯会...

Day40 ( 游戏设计 ) 反弹球 ( 乒乓球 )

反弹球 ( 乒乓球 ) 教学原文参考:反弹球 ( 乒乓球 ) 这篇文章会介绍,如何在 Scratch...