Foreword
全网找不到几个LLVM工具链进行交叉编译的例子,如果只是Linux环境,那可能随便弄,但是Windows下要拉哪个库,环境变量什么的要怎么弄都没找到例子。东拼西凑了几个Blog的内容,总算是完整可以编译了
如果直接用Keil的AC6,那没啥难度,如果抛弃他,改用开源又要怎么弄
Clang-LLVM
GNU GCC编译流程:
源代码 → GCC前端 → GIMPLE(中间表示) → RTL → 汇编代码 → GNU汇编器 → 目标代码
Clang/LLVM编译流程:
源代码 → Clang前端 → LLVM IR → LLVM优化器 → LLVM后端 → 汇编代码 → 目标代码
同时也有各种魔改的类型,使用混合的,Clang的前端+GNU的后端,Clang的前端目前公认是比GNU要快很多的
环境
目前开源可用的LLVM的交叉编译工具链,只看到了这一个
https://github.com/ARM-software/LLVM-embedded-toolchain-for-Arm/releases
官方版本的工具链目前还在建设阶段,根本没有Release可用
https://github.com/arm/arm-toolchain/tree/arm-software
下一个,解压,将bin目录加入到环境变量中
CMake、Make、Ninja(如果需要)也同样需要安装
如果是用MSYS2,可以通过下面的方式安装
安装make
pacman -S make
安装cmake
pacman -S cmake
安装ninja
pacman -S ninja
查看所有安装的包
pacman -Sg
编译
编译工程,以这个为例,他没有说明具体环境怎么弄,而LLVM最麻烦的就是环境问题了
https://github.com/wuwbobo2021/arm-llvm-stm32f103-blinky
他是make构建的整个工程,所以只需要make工具即可,目前LLVM需要人工指定libc、libm的目录或者引用
所以需要这么写
make "ARM_LIB_DIR=I:\llvm\LLVM-ET-Arm-19.1.5-Windows-x86_64\lib\clang-runtimes\arm-none-eabi\armv7m_soft_nofp"
需要指定你的LLVM的armv7m_soft_nofp路径,才能正确找到libc和libm
这样就能正常编译完成了
分析对比
对比一下和arm-gnu-none-eabi的编译有什么区别
# output name
TARGET = arm-llvm-stm32f103-blinky
# debug build?
DEBUG = 0
# optimization
OPT = -O3 -flto
ASM_SOURCES = stm32f10x/startup_stm32f103xb.s
LD_SCRIPT = stm32f103c8tx_flash.ld
C_DIRS = arm \
stm32f10x \
.
C_DEFS = -DUSE_STDPERIPH_DRIVER \
-DSTM32F10X_MD
# 主要这里指定库路径,不知道还没有没有啥办法自动寻找,而不用指定路径
# it must be set for libc and libm, like
# <LLVM Embedded Toolchain for Arm>/lib/clang-runtimes/arm-none-eabi/armv7m_soft_nofp
ARM_LIB_DIR =
# 这个路径却能自动寻找到
# leave empty if the original llvm/clang should be used
ARM_LLVM_PATH =
# 修改替换使用的一些工具
CC = $(ARM_LLVM_PATH)clang
AS = $(ARM_LLVM_PATH)clang
CP = $(ARM_LLVM_PATH)llvm-objcopy
SZ = $(ARM_LLVM_PATH)llvm-size
HEX = $(CP) -O ihex
BIN = $(CP) -O binary -S
OPENOCD = openocd \
-f /usr/share/openocd/scripts/interface/stlink.cfg \
-f /usr/share/openocd/scripts/target/stm32f1x.cfg
GDB = gdb-multiarch
ifeq '$(findstring ;,$(PATH))' ';'
# Windows
RM = del /Q
else
RM = rm -f
endif
# 一些flag的写法或者表示和gnu不太一样了,需要独立区分
FLAGS = -mthumb -mcpu=cortex-m3 --target=thumbv7m-none-unknown-eabi -mfpu=none
C_INCLUDES = $(foreach d, $(C_DIRS), -I$(d)) -I$(ARM_LIB_DIR)/include
C_SOURCES = $(foreach d, $(C_DIRS), $(foreach c, $(wildcard $(d)/*.c), $(c)))
CFLAGS = $(C_DEFS) $(C_INCLUDES) $(FLAGS) -std=c99 -fdata-sections -ffunction-sections
OBJS = $(C_SOURCES:.c=.o) $(ASM_SOURCES:.s=.o)
LD_FLAGS = $(FLAGS) -Wl,--gc-sections -T$(LD_SCRIPT) -nostdlib -lc -lm -L $(ARM_LIB_DIR)/lib
ifeq ($(DEBUG), 1)
C_DEFS += -DDEBUG
CFLAGS += -g -gdwarf-2
else
CFLAGS += $(OPT)
LD_FLAGS += $(OPT)
endif
$(TARGET).bin: $(TARGET).elf
$(BIN) $< $@
$(TARGET).elf: $(OBJS)
$(CC) $(LD_FLAGS) $(OBJS) -o $@
$(SZ) $@
%.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
%.o: %.s
$(AS) -c $(FLAGS) $< -o $@
.PHONY: clean flash debug
clean:
$(RM) $(foreach d, $(C_DIRS), $(d)/*.o) *.elf *.bin
flash: $(TARGET).bin
$(OPENOCD) -c "program $(TARGET).bin preverify verify reset exit 0x08000000"
debug: $(TARGET).elf
$(GDB) -iex "target extended | $(OPENOCD) -c 'gdb_port pipe'" \
-iex 'monitor reset halt' -ex 'break main' -ex 'c' -ex '-' $<
这个的写法是与GNU最大的不同点,他的组合比较固定,没有GNU那么灵活
--target=thumbv7m-none-unknown-eabi -mfpu=none
--target=thumbv7m-unknown-none-eabihf -mfpu=fpv5-d16
如果是在CMakeLists中需要额外指定库文件位置,否则还是会提示问题
# Link directories setup
target_link_directories(${CMAKE_PROJECT_NAME} PRIVATE
# Add user defined library search paths
"I:/llvm/LLVM-ET-Arm-19.1.5-Windows-x86_64/lib/clang-runtimes/arm-none-eabi/armv7m_soft_nofp/include"
)
# Add linked libraries
target_link_libraries(${CMAKE_PROJECT_NAME}
# Add user defined libraries
-l:libc.a
-l:libm.a
)
常见问题
类似这种通不过cmake的test程序,这种都是库没定义好,缺少必要的库,导致连cmake内部的检测程序都无法正常调用编译器完成编译,进而提出错误
LLVM-ET-Arm-19.1.5-Windows-x86_64/bin/clang.exe"
is not able to compile a simple test program.
网上一般有两种解决方案,一种是直接修改对应报错的cmake的测试程序,一种是传递一个宏,让这个检测程序不运行,建议都别用。还是正常解决库路径的问题,否则后面还会有更多问题的
经典问题_exit
没有定义,这个是GNU里,可以使用--specs=nosys.specs
直接靠编译器帮我们实现一个替代,我们就不用写了
ld.lld: error: undefined symbol: _exit
>>> referenced by signal.c:151 ...
>>> referenced by abort.c:63 ...
但是似乎LLVM里没有这个东西,那就得手动实现一个
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
extern "C" {
// 系统调用实现
void _exit(int status) {
while(1) {
// 一个断点,方便调试
__asm__ volatile("bkpt #0");
}
}
int _close(int file) { return -1; }
int _fstat(int file, struct stat *st) {
st->st_mode = S_IFCHR;
return 0;
}
int _isatty(int file) { return 1; }
int _lseek(int file, int ptr, int dir) { return 0; }
int _read(int file, char *ptr, int len) { return 0; }
int _write(int file, char *ptr, int len) { return len; }
// C++ 运行时支持
void __cxa_pure_virtual() { while (1); }
int __cxa_atexit(void (*destructor) (void *), void *arg, void *dso) { return 0; }
void __cxa_finalize(void *f) {}
void *__dso_handle = nullptr;
}
大致类似这样的一个文件即可,如果没有c++就去掉c++部分,其他的适配c
Summary
目前GNU和LLVM都可以正常编译完成整个工程了
对比两种工程编译时间:
LLVM Build completed: 00:04:27.449
GNU Build completed: 00:02:19.536
不知道哪里有问题,导致LLVM实际反而比GNU要慢,甚至慢了一倍
Quote
https://hustlei.github.io/2018/11/msys2-pacman.html
https://github.com/ARM-software/LLVM-embedded-toolchain-for-Arm/blob/main/docs/migrating.md
https://github.com/astronmax/stm32-llvm
https://www.eet-china.com/mp/a103863.html