LV02-03-编译与调试-03-ARM平台的GCC
本文主要是编译与调试——ARM的平台的GCC相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
说明:此篇笔记若有更新,系统环境可能会发生变化,但大概的应该都是一样的。
PC端开发环境 | Windows | Windows11 |
Ubuntu | Ubuntu20.04.6的64位版本 | |
VMware® Workstation 17 Pro | 17.0.0 build-20800274 | |
终端软件 | MobaXterm(Professional Edition v23.0 Build 5042 (license)) | |
Win32DiskImager | Win32DiskImager v1.0 | |
Linux开发板环境 | Linux开发板 | 正点原子 i.MX6ULL Linux 阿尔法开发板 |
uboot | NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) | |
linux内核 | linux-4.15(NXP官方提供) |
点击查看本文参考资料
- 通用
参考资料 | 相关链接 |
--- | --- |
点击查看相关文件下载
--- | --- |
一、准备工作
这一节是直接用的正点原子alpha-imx6ull开发板出厂系统,按照后面搭建好nfs环境,也就是可以在开发板可以挂载vmware中ubuntu系统中的目录。
1. 烧写出厂系统
看这篇笔记《01嵌入式开发/02IMX6ULL平台/LV01-IMX6ULL平台/LV01-02-IMX6ULL-ALPHA开发板体验-03-开发板系统烧写.md》
2. 配置网络环境
看这篇《01嵌入式开发/02IMX6ULL平台/LV02-开发环境/LV02-03-网络环境-01-网络开发环境搭建.md》
还有这篇:《01嵌入式开发/02IMX6ULL平台/LV01-IMX6ULL平台/LV01-02-IMX6ULL-ALPHA开发板体验-04-基本功能测试.md》,主要是参考usb wifi测试那部分,完成开发板联网,方便后面在开发板下载gcc编译器。
3. NFS环境搭建
看这篇《01嵌入式开发/02IMX6ULL平台/LV02-开发环境/LV02-03-网络环境-03-NFS环境搭建.md》
4. arm平台GCC
看这篇笔记《01嵌入式开发/02IMX6ULL平台/LV02-开发环境/LV02-01-交叉编译工具链.md》。这里安装的是这个4.9.4
版本的交叉编译工具软件包:Linaro Releases
1 | https://releases.linaro.org/components/toolchain/binaries/4.9-2017.01/arm-linux-gnueabihf/ |
5. 测试文件
LV01_GCC_COMPILE/00_compile/08_armgcc_x86_64/hello.c · 苏木/imx6ull-app-demo - 码云 - 开源中国 (gitee.com)
1 |
|
二、在ARM板上运行x86_64平台的程序
1. 先做个测试
我们把ubuntu的gcc编译的测试程序放到开发板跑一下:
1 | gcc hello.c -o hello -Wall |
然后我们在开发板中执行:
发现程序无法正常运行,终端提示ARM开发板在执行x86架构(Intel或AMD)的hello程序时提示格式错误,原因是x86_64和ARM架构的程序不兼容,本质是由于这些CPU使用的指令集不同。这就是为什么需要针对芯片架构定制文件系统、软件工具,apt使用的源也针对不同架构提供不同的软件包。
当然,对于由Python等跨平台语言编写的源程序,它们不需要编译,只需要使用匹配的解释器即可运行,与芯片架构甚至操作系统无关。
2. ARM板上的GCC
我们使用的开发板若是性能允许的话,是可以直接在开发板安装gcc的,就像在ubuntu中一样,只是开发板需要联网,并且支持这个apt下载:
1 | sudo apt install gcc -y |
或者自己移植一个,但是把,没啥必要,这里了解一下就行了。这里用一下野火教程(教程用的是debian根文件系统)中的图:
GCC工具链包括了binutils、readelf工具,因此GCC安装完成后,binutils、readelf等工具也可以直接使用。运行以下指令查看GCC版本与安装路径。
1 | gcc -v #查看gcc编译器版本 |
与前面介绍的Ubuntu中GCC不一样的是,开发板中gcc编译工具链的目标平台是arm架构的,表示它生成的应用程序只能运行于ARM平台的开发板, 而不适合用于X86平台。然后我们创建一个hello.c测试demo,把上面的代码复制进去,然后编译一下:
1 | 在Debian的hello_c目录下执行如下命令 |
3. 交叉编译工具链
3.1 交叉编译
我们编译了两个程序,分别是:
- 编译器运行在X86_64架构平台上,编译生成X86_64架构的可执行程序
- 编译器运行在ARM架构平台上,编译生成ARM架构的可执行程序
这种编译器和目标程序都是相同架构的编译过程,被称为 本地编译 。而当前我们希望的是编译器运行在x86架构平台上,编译生成ARM架构的可执行程序,这种编译器和目标程序运行在不同架构的编译过程,被称为 交叉编译。
既然已经有本地编译,为什么需要交叉编译?这是因为通常编译工具链对编译环境有较高的要求,编译复杂的程序时,可能需要巨大的存储空间以及强大的CPU运算能力加快编译速度。常见的ARM 架构平台资源有限,无论是存储空间还是CPU运算能力,都与X86平台相去甚远,特别是对于MCU平台,安装编译器根本无从谈起。有了交叉编译,我们就可以在PC上快速编译出针对其他架构的可执行程序。
相对的,能进行架构“交叉”编译过程的编译器,就被称为 交叉编译器(Cross compiler)。 交叉编译器听起来是个新概念,但在MCU开发中一直使用的就是交叉编译器, 例如开发STM32、RT1052所使用的IDE软件Keil(MDK)或IAR,就是在Windows x86架构编译,生成MCU平台的应用程序,最后下载到芯片执行。
3.2 安装ARM-GCC
安装交叉编译工具链有如下三种方式:
- 直接在Ubuntu下使用APT包管理工具下载安装,操作简单。
- 自行下载第三方制作好的工具链,如Linaro,好处是选择丰富,能找到很多不同的版本。(像之前我就装过一个)
- 使用crosstool-ng根据需要自己制作,过程复杂,不推荐。
使用apt管理工具下载安装时命令如下:
1 | 在主机上执行如下命令 |
不过我这里用的是自己从Linaro官网下载的:
版本是这样的:
1 | 使用内建 specs。 |
后边会用到另一种软浮点类型的交叉编译工具,这里直接也安装一下,我们打开Linaro Releases
选择这个不带hf的下载安装,我们同样选择4.9版本:
1 | cd ~/2software/ |
我们重新开一个终端,看一下版本:
1 | sumu@sumu-virtual-machine:~$ arm-linux-gnueabi-gcc -v |
所以目前,我的虚拟机中有两个交叉编译工具链:
3.3 交叉编译hello.c
交叉编译器与本地编译器使用起来并没有多大区别。对于源文件的编译过程,都是四个阶段:预处理,编译,汇编以及链接,区别只在于编译工具。因此,我们可以依葫芦画瓢,修改一下前面GCC编译章节的命令,就可以完成这个过程。
在ubuntu上执行如下命令对Hello World程序进行交叉编译:
1 | 以下命令在主机上运行 |
然后我们直接执行:
发现是不行的,我们到开发板试一下:
3.4 readelf查看一下
同样的C代码文件,使用交叉编译器编译后,生成的hello已经变成了ARM平台的可执行文件,可以通过readelf工具来查看具体的程序信息。
看到hello程序的系统架构为ARM平台,标志中表明了它是hard-float类型的EABI接口。
三、如何选择编译器
1. 去哪下载编译器?
除了我们安装的arm-linux-gnueabihf-gcc外,编译器还有很多版本,如arm-linux-gnueabi-gcc。目前大部分ARM开发者使用的都是由Linaro组织提供的交叉编译器,包括前面使用APT安装的ARM-GCC工具链,它的来源也是Linaro。Linaro是由ARM发起,与其它ARM SOC公司共同投资的非盈利组织。我们可以在它官网的如下地址找到它提供的ARM交叉编译器:Linaro Releases
2. 怎么选择?
编译器的命名没有严格的规则,但它们的名字中一般包含我们最关心的内容,可根据它们的名字选择要使用的编译器:
1 | arch [-os] [-(gnu)eabi(hf)] -gcc |
其中的各字段如下所示:
字段 | 含义 |
---|---|
arch | 目标芯片架构 |
os | 操作系统 |
gnu | C标准库类型 |
eabi | 应用二进制接口 |
hf | 浮点模式 |
以我们安装的arm-linux-gnueabihf-gcc编译器为例,表示它的目标芯片架构为ARM,目标操作系统为Linux,使用GNU的C标准库即glibc,使用嵌入式应用二进制接口(eabi),编译器的浮点模式为硬浮点hard-float。而另一种名为arm-linux-gnueabi- gcc的编译器与它的差别就在于是否带“hf”,不带“hf”表示它使用soft-float模式。
2.1 目标芯片架构
目标芯片架构就是指交叉编译器生成的程序运行的平台,如ARM、MIPS,其中ARM交叉编译器又分为ARMv7、ARMv8及aarch64架构。这里使用的i.MX 6ULL的内核为Cortex-A7,它使用的是ARMv7架构。 arm-linux-gnueabihf-gcc直接以arm表示ARMv7架构。
2.2 大小端
目标芯片的大小端模式,i.MX 6ULL使用的是小端模式。若是大端模式(big edian),编译器名字中会带“be”或“eb”字段进行标标注。
2.3 目标操作系统
目标操作系统表示编译后的程序运行的系统,主要有Linux或bare-metal(无操作系统)两种,arm-linux-gnueabi-gcc 表示它目标程序的运行环境为Linux系统,程序可以使用Linux下的C标准库或Linux内核提供的API,如fork等进程函数。而arm- eabi-gcc或arm-none-eabi-gcc表示它们的目标程序运行在无操作系统的环境中。
所以严格来说,我们编译Linux应用程序时应该使用带“linux”的编译器,而编译uboot、裸机程序时,应该使用“bare-metal”类型的裸机编译器,但很多开发者常常把它们混用也没有出现问题,这一般是因为开发者编写的裸机程序本身就没有使用到Linux系统提供的API,所以才不会出错。
2.4 C标准库类型
C标准库类型通常有gnu、uclibc等,分别表示GNU的glibc库和uclibc库,这取决于目标操作系统提供的C库类型,不过由于glibc和uclibc库是兼容的,所以开发者在编通常直接使用GNU类型的编译器而不管目标系统中的C库类型。 除了裸机编译器不带C库之外,其它编译器的C库类型都是glibc库的,如arm-linux-gnueabihf-gcc。
2.5 应用二进制接口
应用二进制接口(Application Binary Interface),描述了应用程序和操作系统之间或其他应用程序的低级接口。在编译器选项中主要有“abi”和“eabi”两种类型,abi通常用在x86架构上,而eabi表示embed abi,即嵌入式架构,如ARM、MIPS等。
2.6 浮点模式
部分ARM处理器带浮点运算单元,代码需要进行浮点运算时若交给fpu处理,可以加快运算速度。编译器针对浮点运算的不同处理情况提供了以下几种模式:
- hard: 硬浮点类型(hard-float),采用fpu参与浮点运算。 arm-linux-gnueabihf-gcc、armeb-linux-gnueabihf-gcc都是硬浮点类型,即名字中带“hf”。
- soft:软浮点类型(soft-float),即使有fpu浮点运算单元也不用,而是使用软件模式,arm-linux-gnueabi-gcc、armeb-linux-gnueabi-gcc都是软浮点类型,即名字中不带“hf”。
- softfp:允许使用浮点指令,但保持与软浮点ABI的兼容性。
i.MX6ULL带有fpu,对于soft-float和hard-float模式都支持,不过开发板中提供Linux文件系统中的库若是都是使用hard模式编译的,那么编写应用程序时也需要使用相同类型的编译器,否则会应用程序运行时会提示找不到库文件。
2.7 编译器版本号
通常来说高版本的编译器是向后兼容的,但开发特定程序时会使用固定的某个版本编译器,所以程序可能会依赖该版本的编译器,根据自己要编译的程序的要求选择即可。
四、编译器类型对程序的影响
前面学习编译器类型时提到,编译器名字中带hf和不带hf的差异为硬浮点和软浮点模式,此处我们实际验证一下,对比两种编译器对同样程序的影响。
1. 编译两种程序
- 软浮点动态编译的程序
1 | arm-linux-gnueabi-gcc hello.c -o a_gnueabi.out -Wall |
- 硬浮点动态编译的程序
1 | arm-linux-gnueabihf-gcc hello.c -o a_gnueabihf.out -Wall |
2. 运行两个程序
a_gnueabi.out
a_gnueabihf.out
使用arm-linux-gnueabi-gcc软浮点编译的程序无法正常执行,它提示找不到文件或目录,这是因为程序内部调用了软浮点的类库(如glibc库文件libc.so.6),而开发板配套的库文件是硬浮点类型的。
3. 开发板的glibc库类型
关于库文件的类型,同样可以使用readelf工具查看,在开发板中执行以下命令:
1 | 使用readelf查看开发板的libc.so.6类型 |
我使用的正点原子出厂系统并不带这个工具我们可以拷贝到nfs挂载目录去,通过ubuntu中的工具查看
我们来看一下:
表示开发板的glibc库文件libc.so.6为ARM架构的hard-float类型库,所以不带hf编译器生成的hello程序与它不兼容,无法正常运行。
4. 运行软浮点静态编译的程序
既然软浮点动态编译的程序是因为库不兼容,那如果程序使用静态编译,即程序自带相关库的内容,是不是就可以正常运行呢?答案是可以的。我们继续进行如下测试:
1 | 以下命令在主机上运行 |
可看到使用静态编译得到的a_gnueabi_static.out
程序比动态编译的a_gnueabi.out
大,这是因为它自身包含了库文件,使用readelf也可以看到 a_gnueabi_static.out
程序依然是soft-float类型的。我们拷贝到开发板运行一下:
这就是编译器及系统库文件对程序运行的影响。