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/
image-20220512190533043

5. 测试文件

LV01_GCC_COMPILE/00_compile/08_armgcc_x86_64/hello.c · 苏木/imx6ull-app-demo - 码云 - 开源中国 (gitee.com)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

int main(int argc, const char *argv[])
{
int i = 0;
/* code */
printf("hello, world! This is a C program.\n");
for(i = 0; i < 10; i++)
{
printf("output i=%d\n",i);
}

return 0;
}

二、在ARM板上运行x86_64平台的程序

1. 先做个测试

我们把ubuntu的gcc编译的测试程序放到开发板跑一下:

1
2
gcc hello.c -o hello -Wall
cp -pvf hello ~/4nfs/app/

然后我们在开发板中执行:

image-20241030184212314

发现程序无法正常运行,终端提示ARM开发板在执行x86架构(Intel或AMD)的hello程序时提示格式错误,原因是x86_64和ARM架构的程序不兼容,本质是由于这些CPU使用的指令集不同。这就是为什么需要针对芯片架构定制文件系统、软件工具,apt使用的源也针对不同架构提供不同的软件包。

当然,对于由Python等跨平台语言编写的源程序,它们不需要编译,只需要使用匹配的解释器即可运行,与芯片架构甚至操作系统无关。

2. ARM板上的GCC

我们使用的开发板若是性能允许的话,是可以直接在开发板安装gcc的,就像在ubuntu中一样,只是开发板需要联网,并且支持这个apt下载:

1
2
sudo apt install gcc -y
sudo apt-get install gcc -y

或者自己移植一个,但是把,没啥必要,这里了解一下就行了。这里用一下野火教程(教程用的是debian根文件系统)中的图:

GCC安装编译工具链

GCC工具链包括了binutils、readelf工具,因此GCC安装完成后,binutils、readelf等工具也可以直接使用。运行以下指令查看GCC版本与安装路径。

1
2
gcc -v          #查看gcc编译器版本
which gcc #查看gcc的安装路径
Debian GCC

与前面介绍的Ubuntu中GCC不一样的是,开发板中gcc编译工具链的目标平台是arm架构的,表示它生成的应用程序只能运行于ARM平台的开发板, 而不适合用于X86平台。然后我们创建一个hello.c测试demo,把上面的代码复制进去,然后编译一下:

1
2
3
4
5
6
7
8
#在Debian的hello_c目录下执行如下命令
gcc hello.c –o hello #使用gcc把hello.c编译成hello程序

ls #查看目录下的文件
./hello #执行生成的hello程序

#若提示权限不够或不是可执行文件,执行如下命令再运行hello程序
chmod u+x hello #给hello文件添加可执行权限
GCC编译结果

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
2
3
4
5
6
# 在主机上执行如下命令
sudo apt install gcc-arm-linux-gnueabihf
sudo apt install gcc-arm-linux-gnueabi
# 安装完成后使用如下命令查看版本
arm-linux-gnueabihf-gcc -v
arm-linux-gnueabi-gcc -v

不过我这里用的是自己从Linaro官网下载的:

image-20241030190450042

版本是这样的:

1
2
3
4
5
6
7
使用内建 specs。
COLLECT_GCC=arm-linux-gnueabihf-gcc
COLLECT_LTO_WRAPPER=/home/sumu/2software/gcc-linaro-4.9.4/bin/../libexec/gcc/arm-linux-gnueabihf/4.9.4/lto-wrapper
目标:arm-linux-gnueabihf
配置为:#这里省略...
线程模型:posix
gcc 版本 4.9.4 (Linaro GCC 4.9-2017.01)

后边会用到另一种软浮点类型的交叉编译工具,这里直接也安装一下,我们打开Linaro Releases

image-20241030193314574

选择这个不带hf的下载安装,我们同样选择4.9版本:

1
2
3
4
5
6
7
8
9
cd ~/2software/
wget -c https://releases.linaro.org/components/toolchain/binaries/4.9-2017.01/arm-linux-gnueabi/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi.tar.xz

tar xvf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi.tar.xz
rm -rf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi.tar.xz

echo "export PATH=$PATH:/home/sumu/2software/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi/bin" >> ~/.bashrc

source ~/.bashrc

我们重新开一个终端,看一下版本:

1
2
3
4
5
6
7
8
sumu@sumu-virtual-machine:~$ arm-linux-gnueabi-gcc -v
使用内建 specs。
COLLECT_GCC=arm-linux-gnueabi-gcc
COLLECT_LTO_WRAPPER=/home/sumu/2software/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi/bin/../libexec/gcc/arm-linux-gnueabi/4.9.4/lto-wrapper
目标:arm-linux-gnueabi
配置为:#这里省略...
线程模型:posix
gcc 版本 4.9.4 (Linaro GCC 4.9-2017.01)

所以目前,我的虚拟机中有两个交叉编译工具链:

image-20241030194149311

3.3 交叉编译hello.c

交叉编译器与本地编译器使用起来并没有多大区别。对于源文件的编译过程,都是四个阶段:预处理,编译,汇编以及链接,区别只在于编译工具。因此,我们可以依葫芦画瓢,修改一下前面GCC编译章节的命令,就可以完成这个过程。

在ubuntu上执行如下命令对Hello World程序进行交叉编译:

1
2
3
# 以下命令在主机上运行
# 在hello.c程序所在的目录执行如下命令
arm-linux-gnueabihf-gcc hello.c

然后我们直接执行:

image-20241030190749224

发现是不行的,我们到开发板试一下:

image-20241030191029674

3.4 readelf查看一下

同样的C代码文件,使用交叉编译器编译后,生成的hello已经变成了ARM平台的可执行文件,可以通过readelf工具来查看具体的程序信息。

image-20241030190958082

看到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

image-20241030192355613

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
2
arm-linux-gnueabi-gcc hello.c -o a_gnueabi.out -Wall
cp -pv a_gnueabi.out ~/4nfs/app/
  • 硬浮点动态编译的程序
1
2
arm-linux-gnueabihf-gcc hello.c -o a_gnueabihf.out -Wall
cp -pv a_gnueabihf.out ~/4nfs/app/
image-20241030195033371

2. 运行两个程序

  • a_gnueabi.out
image-20241030195112625
  • a_gnueabihf.out
image-20241030195141504

使用arm-linux-gnueabi-gcc软浮点编译的程序无法正常执行,它提示找不到文件或目录,这是因为程序内部调用了软浮点的类库(如glibc库文件libc.so.6),而开发板配套的库文件是硬浮点类型的。

3. 开发板的glibc库类型

关于库文件的类型,同样可以使用readelf工具查看,在开发板中执行以下命令:

1
2
3
# 使用readelf查看开发板的libc.so.6类型

readelf -h /lib/libc.so.6

我使用的正点原子出厂系统并不带这个工具我们可以拷贝到nfs挂载目录去,通过ubuntu中的工具查看

image-20241030195500369

我们来看一下:

image-20241030195624016

表示开发板的glibc库文件libc.so.6为ARM架构的hard-float类型库,所以不带hf编译器生成的hello程序与它不兼容,无法正常运行。

4. 运行软浮点静态编译的程序

既然软浮点动态编译的程序是因为库不兼容,那如果程序使用静态编译,即程序自带相关库的内容,是不是就可以正常运行呢?答案是可以的。我们继续进行如下测试:

1
2
3
4
5
6
#以下命令在主机上运行

# 使用不带hf的编译器对hello.c进行静态编译,生成的程序名为 a_gnueabi_static.out
arm-linux-gnueabi-gcc hello.c -o a_gnueabi_static.out --static -Wall
ls -lh # 查看生成的程序大小
readelf -h a_gnueabi_static.out # 查看hello_static文件头
image-20241030195956100

可看到使用静态编译得到的a_gnueabi_static.out程序比动态编译的a_gnueabi.out大,这是因为它自身包含了库文件,使用readelf也可以看到 a_gnueabi_static.out程序依然是soft-float类型的。我们拷贝到开发板运行一下:

image-20241030200140743

这就是编译器及系统库文件对程序运行的影响。