I.MX6U处理器LED灯点亮汇编程序之命令行方式编译与链接

2019-11-19 12:21发布

如果你是在 Windows 下使用 Source Insight 编写的代码,就需要通过 FileZilla 将编写好的代码发送的Ubuntu 中去编译,FileZilla 的使用参考我们前面的文章《嵌入式Linux开发学习之 Ubuntu 和 Windows 文件互传》。因为我们现在是直接在 Ubuntu 下使用VSCode 编译的代码,所以不需要使用 FileZilla 将代码发送到Ubuntu 下,可以直接进行编译,在编译之前我们先了解几个编译工具。

1、arm-linux-gnueabihf-gcc 编译文件

我们是要编译出在 ARM 开发板上运行的可执行文件,所以要使用交叉编译器 arm-linux-gnueabihf-gcc 来编译。因此本实验就一个 led.s 源文件,所以编译比较简单。先将 led.s 编译为对应的.o 文件,在终端中输入如下命令:

arm-linux-gnueabihf-gcc -g -c led.s -o led.o

上述命令就是将 led.s 编译为 led.o,其中“-g”选项是产生调试信息,GDB 能够使用这些调试信息进行代码调试。“-c”选项是编译源文件,但是不链接。“-o”选项是指定编译产生的文件名字,这里我们指定 led.s 编译完成以后的文件名字为 led.o。执行上述命令以后就会编译生成一个 led.o 文件,如图所示:

b491664cf211442db7f4e2c5accb3cb9

编译生成 led.o 文件

图中 led.o 文件并不是我们可以下载到开发板中运行的文件,一个工程中所有的 C文件和汇编文件都会编译生成一个对应的.o 文件,我们需要将这.o 文件链接起来组合成可执行文件。

2、arm-linux-gnueabihf-ld 链接文件

arm-linux-gnueabihf-ld 用来将众多的.o 文件链接到一个指定的链接位置。我们在学习SMT32 的时候基本就没有听过“链接”这个词,我们一般用 MDK 编写好代码,然后点击“编译”,MDK 或者 IAR 就会自动帮我们编译好整个工程,最后再点击“下载”就可以将代码下载到开发板中。这是因为链接这个操作 MDK 或者 IAR 已经帮你做好了,后面我就以 MDK 为例给大家讲解。大家可以打开一个 STM32 的工程,然后编译一下,肯定能找到很多.o 文件,如图所示:

1bd3a15d945542af88741a68ea1104cb

STM32 编译生成的.o 文件

上图中的这些.o 文件肯定会被 MDK 链接到某个地址去,如果使用 MDK 开发 STM32的话肯定对下图中所示界面很熟悉:

dbde24c0b4a24e49b7a35f0a6537f81c

STM32 配置界面

图中左侧的 IROM1 我们都知道是设置 STM32 芯片的ROM 起始地址和大小的,右边的 IRAM1 是设置 STM32 芯片的RAM 起始地址和大小的。其中 0X08000000 就是 STM32 内部 ROM 的起始地址,编译出来的指令肯定是要从 0X08000000 这个地址开始存放的。对于STM32 来说 0X08000000 就是它的链接地址,图中的这些.o 文件就是这个链接地址开始依次存放,最终生成一个可以下载的 hex 或者 bin 文件,我们可以打开.map 文件查看一下这些文件的链接地址,在 MDK 下打开一个工程的.map 文件方法如图所示:

dbedd0f3f01c4ec0bec9f9e5ac3f04be

.map 文件打开方法

图中的.map 文件就详细的描述了各个.o 文件都是链接到了什么地址,如图所示:

23fb9bc2df684b2eb9fe1e1d3ba37013

STM32 镜像映射文件

从图中就可以看出 STM32 的各个.o 文件所处的位置,起始位置是 0X08000000。由此可以得知,我们用 MDK 开发 STM32 的时候也是有链接的,只是这些工作 MDK 都帮我们全部做好了,我们不用关心而已。但是我们在 Linux 下用交叉编译器开发 ARM 的是时候就需要自己处理这些了。

因此我们现在需要做的就是确定一下本试验最终的可执行文件其运行起始地址,也就是链接地址。这里我们要区分“存储地址”和“运行地址”这两个概念,“存储地址”就是可执行文件存储在哪里,可执行文件的存储地址可以随意选择。“运行地址”就是代码运行的时候所处的地址,这个我们在链接的时候就已经确定好了,代码要运行,那就必须处于运行地址处,否则代码肯定运行出错。比如 I.MX6U 支持 SD 卡、EMMC、NAND 启动,因此代码可以存储到 SD 卡、EMMC 或者 NAND 中,但是要运行的话就必须将代码从 SD 卡、EMMC 或者NAND 中拷贝到其运行地址(链接地址)处,“存储地址”和“运行地址”可以一样,比如STM32 的存储起始地址和运行起始地址都是 0X08000000。

本教程所有的裸机例程都是烧写到 SD 卡中,上电以后 I.MX6U 的内部 boot rom 程序会将可执行文件拷贝到链接地址处,这个链接地址可以在 I.MX6U 的内部 128KB RAM 中(0X900000~0X91FFFF),也可以在外部的 DDR 中。本教程所有裸机例程的链接地址都在 DDR中,链接起始地址为 0X87800000。I.MX6U-ALPHA 开发板的 DDR 容量有两种:512MB 和256MB,起始地址都为 0X80000000,只不过 512MB 的终止地址为 0X9FFFFFFF,而 256MB 容量的终止地址为 0X8FFFFFFF。之所以选择 0X87800000 这个地址是因为后面要讲的 Uboot 其链接地址就是 0X87800000,这样我们统一使用 0X87800000 这个链接地址,不容易记混。

确定了链接地址以后就可以使用 arm-linux-gnueabihf_ld 来将前面编译出来的 led.o 文件链接到 0X87800000 这个地址,使用如下命令:

arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf

上述命令中-Ttext 就是指定链接地址,“-o”选项指定链接生成的 elf 文件名,这里我们命名为 led.elf。上述命令执行完以后就会在工程目录下多一个 led.elf 文件,如图所示:

faed5797185f44f0bffe9183bf988561

链接生成 led.elf 文件

led.elf 文件也不是我们最终烧写到 SD 卡中的可执行文件,我们要烧写的.bin 文件,因此还需要将 led.elf 文件转换为.bin 文件,这里我们就需要用到 arm-linux-gnueabihf-objcopy 这个工具了。

3、arm-linux-gnueabihf-objcopy 格式转换

arm-linux-gnueabihf-objcopy 更像一个格式转换工具,我们需要用它将 led.elf 文件转换为led.bin 文件,命令如下:

arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin

上述命令中,“-O”选项指定以什么格式输出,后面的“binary”表示以二进制格式输出,选项“-S”表示不要复制源文件中的重定位信息和符号信息,“-g”表示不复制源文件中的调试信息。上述命令执行完成以后,工程目录如图所示:

a2c0b09eda964768a4a9c61cf3575af7

生成最终的 led.bin 文件

至此我们终于等到了想要的东西——led.bin 文件。

4、arm-linux-gnueabihf-objdump 反汇编

大多数情况下我们都是用 C 语言写试验例程的,有时候需要查看其汇编代码来调试代码,因此就需要进行反汇编,一般可以将 elf 文件反汇编,比如如下命令:

arm-linux-gnueabihf-objdump -D led.elf > led.dis

上述代码中的“-D”选项表示反汇编所有的段,反汇编完成以后就会在当前目录下出现一个名为 led.dis 文件,如图所示:

89f014be515c4528aed1418c912b91b4

反汇编生成 led.dis

可以打开 led.dis 文件看一下,看看是不是汇编代码,如图所示:

5c74ef9abe424610a8626b57caaa50a8

反汇编文件

从图中可以看出 led.dis 里面是汇编代码, 而且还可以看到内存分配情况。在0X87800000 处就是全局标号_start,也就是程序开始的地方。通过 led.dis 这个反汇编文件可以明显的看出到我们的代码已经链接到了以 0X87800000 为起始地址的区域。

总结一下我们为了编译ARM 开发板上运行的 led.o 这个文件使用了如下命令:

arm-linux-gnueabihf-gcc -g -c led.s -o led.o

arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf arm-linux-gnueabihf-objcopy -O binary -S

-g led.elf led.bin arm-linux-gnueabihf-objdump -D led.elf > led.dis