概述
GCC在最初指GNU C Compiler,作为GNU计划中相当重要的一环,在GNU发展初期只支持C语言,这是被当时的编程语言发展限制的。随后编程语言爆发式增加,也极大地促进了GCC的扩展,使得GCC编译器可以使用C++、Fortran、Pascal、Java等语言,也可以针对不同处理器架构如x86、ARM、MIPS、PowerPC等,编译汇编语言程序。此时的GCC不再是单一的C语言、适用于x86体系架构的编译工具链,于是又改名为GUN Compiler Collec
tion,意为GNU编译器套件,每一种GCC编译器又由多种工具组成,所以又可以叫GCC工具链。
命名
由于GCC种类繁多,在命名时通常为arch[-vendor][-os][-abi]-gcc(这个gcc也可以放在最前面,因为这样也不会产生歧义),中括号中可以缺省。
arch为目标主机架构,是编译产生的二进制程序运行的平台。
vendor是对应GCC工具链的提供商,在GNU的大环境下,我们一般不关心这一部分。
os是目标主机上运行的程序,裸机工具链为-none,链接时使用newlib库,且无法使用与操作系统相关的函数(当然使用源码参与编译的小型嵌入式操作系统不在此列),带有操作系统如Linux的话,就是-linux,链接时使用Glibc库,可以使用像fork()这样由操作系统提供的函数。
abi是二进制程序接口类型,指定了文件格式、数据类型和寄存器使用,可以认为是函数调用的约定,在这样的约定下,可以使用汇编与C混合编程、相互调用。对于普通PC机来说,这部分是-abi,对于嵌入式平台来说,这部分是-eabi。
对于
STM32单片机而言,它是ARM Cortex-M架构,无法运行大型操作系统(认为是裸机开发),使用嵌入式abi。对应的工具链可以选用arm-none-eabi-gcc,名称中的-none是操作系统类型,表示裸机运行,供应商信息-vendor被省略。
使用命令
参数flags
虽然GCC编译器多种多样,但它们都是在GCC的规范下进一步开发完善所得到的,所以支持绝大部分GCC的参数和命令,并在此基础上,新增了一些有针对性的参数。
GCC的通用参数有:
编译但不链接-c
预处理但不编译-E
生成汇编语言文件-S
输出-o
选择语言标准-std=
使能所有警告-Wall
添加头文件目录-I
对于arm-none-eabi-gcc,新增的参数有:
指定cpu名称-mcpu=
指定FPU-mfpu=
arm-none-eabi-gcc的生成过程遵循GCC规范,即编译和链接:
编译过程是将代码文件*.c转换位对象文件*.o的过程;
链接过程是把对象文件和库lib*.a合并成目标文件的过程,这个目标文件可以是另一个库,也可以是可执行文件。
编译过程中,需要项编译器传递三个参数:编译选项、编译信息和文件路径。
编译选项用于向编译器传递编译采取的动作,以及硬件信息等,如采用什么CPU、什么FPU、优化选项、调试选项等。编译信息用于向编译器传递头文件路径、定义的宏。文件路径即参与编译的文件路径和输出文件的路径。这样分类的依据是:编译选项由目标主机确定,与代码的关联性较弱;编译信息具有通用性,所有文件可以使用同一份编译信息;文件路径指示了参与编译的文件。
链接过程中,需要向编译器(然后编译器调用链接器完成链接)传递链接选项、链接脚本和文件路径。
链接选项的作用和编译选项类似,也是告诉链接器采取什么动作。链接脚本的作用是告诉链接器应该采取什么样的地址空间形式,因为储存区域排布在0x0000_0000 - 0xFFFF_FFFF这4GB的地址上,并不是所有地址都能够使用,所以需要链接脚本来描述地址空间。文件路径和编译一样,指示了参与链接的对象。
编译命令arm-none-eabi-gcc
arm-none-eabi-gcc的编译指令为:
arm-none-eabi-gcc (CFLAGS) -c *.c -o *.o
其中:
-c *.c表示编译*.c这个C语言源文件
-o *.o表示将编译结果输出到*.o这个对象文件
(CFLAGS)的内容为编译信息和一部分的编译选项,
(CFLAGS)由以下几部分组成:
硬件信息-mcpu=xxx -mfpu=xxx -mfloat-abi=xxx [-mthumb -mthumb-interwork]
对于GCC-arm工具链(arm-none-eabi-gcc和其他类似的GCC-arm工具链),指定硬件信息是必要的。GCC-arm工具链可以用于多种架构的代码编译,而不同架构又需要对应不同的指令集、不同的FPU方案等。arm-none-eabi-gcc编译器的硬件信息应该由-march和-mtune给出,-march指定ARM架构版本,-mtune指出ARM处理器名称,但是如果指定了-mcpu,arm-none-eabi-gcc编译器就可以自动推导出-march和-mtune。
-mfpu和-mfloat-abi都用于指定浮点运算相关的细节,其中-mfpu指定了FPU硬件信息。-mfloat-abi指定了浮点运算接口,可以用soft来指定使用GCC浮点库、用softfp允许使用浮点指令集,但仍然使用GCC浮点库,或用hard指示使用浮点指令集,由FPU进行运算。
-mthumb用于指定生成thumb指令集的代码,这是一个可选参数,默认情况下,arm-none-eabi-gcc会优先产生ARM指令集的代码。
-mthumb-interwork允许编译器进行ARM指令和Thumb指令的相互调用,默认情况下,这个选项是未允许的。
其他选项-std=xxx -Wall -On -g -gdwarf-2 -fdata-sections -ffunction-sections
-std=xxx用于指定参与编译的代码使用什么语言标准,一般来说只要新一点就可以,比如使用gnu11标准。
-Wall表示编译过程中所有遇到的警告全部输出,但不会强制停止编译并报错。
-On是优化选项,n的值可以取0-3,取值越大优化程度越深,生成的代码也通常更小。但是优化程度过深可能使得程序出错。
-g -gdwarf-2是调试选项,其中-g表示在生成的文件中添加调试信息,-gdwarf-2表示调试信息的格式为DWARF,版本号为2。调试信息将在对应的GDB(比如arm-none-eabi-gdb)中使用。
-fdata-sections -ffunction-sections用于代码的分割和裁剪,会将每一个函数都拆分成.text、.rodata、.data、.bss段,这部分和对象文件的链接有关。加上这两个参数,配合链接器可以去除代码中无用的部分,减少代码大小。如果没有这两个参数,编译器就会按文件分段而不是按照函数分段。
头文件路径-Ipath和宏-Dmacro[=expression]。
这两项和C语言的语法有关。编译器默认只会在编译器头文件目录和当前目录下引用头文件,并不能自动地引用其他目录下的头文件,需要用-I选项将目录加入编译器的头文件目录列表中,否则会因为找不到呗引用的头文件而报错。-D可以将一个宏添加到当前文件的宏列表中,其中方括号内的表达式为可选内容,即被宏替换后的内容
链接命令 arm-none-eabi-ld
arm-none-eabi-gcc的链接指令为
arm-none-eabi-ld (LDFLAGS) (OBJ) -o *.elf
(OBJ)表示参与链接的所有对象文件(*.o),-o *elf表示将可执行文件以ELF(Executable and Link Format)格式输出,这个文件并不是可执行文件,需要用arm-none-eabi-objcopy工具导出为*.bin或者*.hex文件。(LDFLASG)是链接选项和链接脚本,为了方便,可以把这两部分写在一起。
(LDFLAGS)由以下几部分组成:
硬件信息-mcpu=xxx -mfpu=xxx -mfloat-abi=xxx [-mthumb -mthumb-interwork]
这部分通常和链接保持一致,功能也是相同的。
外部库链接-llib
这个选项可以链接一些外部可选的库,比如GCC默认main()由exit()终止,也就是GCC会以执行exit(main())的形式调用main(),所以需要加入libc.a和libnosys.a编译,否则会报错,对应指令为-lc -lnosys,链接器会自动添加前缀lib和拓展名.a。
链接脚本-Txxx
链接脚本是对地址空间的描述信息,与PC机不同,裸机开发时一般没有MMU或者不使用MMU,无法进行地址映射,绝大部分情况下都是直接使用物理地址,所以二进制指令的编排也需要按照物理地址进行。对于STM32而言,ROM从0x0800 0000开始编址,RAM从0x2000 0000开始编制,若由其他的ROM和RAM,也会用对应的其他地址开始编址。
其他选项-Wl--GC-sections
这是向链接器传递链接选项的选项,实际传入链接器的参数为-GC-sections,这个参数会去除没有被引用的段,以减少代码大小。但前提是编译过程中使用了-fdata-sections -ffunction-sections,否则会链接编译生成的所有段,即使没有被引用的段也会参与链接,不会起减少代码大小的作用。
经过了编译了链接之后,通常得到的是一个*.elf文件,它包含了可执行代码的全部信息。为了得到最终的可执行文件,或是进行调试等,还需要其它操作。
查询储存空间占用信息命令arm-none-eabi-size
arm-none-eabi-size *.elf
这条指令会显示*.elf中.text段、.data段和.bss段的大小
text data bss dec hex filename
121252 576 2984 124812 1e78c firmware.elf
ROM占用为.text+.data,RAM占用为.data+.bss
导出*.bin 命令arm-none-eabi-objcopy
arm-none-eabi-objcopy -O binary -S *.elf *.bin
-O binary表示导出二进制文件,-S表示去除所有符号和重定位信息,因为这些信息在程序执行过程中是不需要的。
调试程序命令arm-none-eabi-gdb
arm-none-eabi-gdb *.elf
这条指令可以使用GDB对*.elf进行调试。*.elf包含了可执行代码的全部信息,所以可以直接使用。不过通常只有GDB是无法调试的,还需要openocd配合调试器进行调试。
反汇编命令arm-none-eabi-objdump
arm-none-eabi-objdump -D *.elf > *.txt
-D表示反汇编所有内容,不过由于objdump的内容是直接输出到控制台的,还需要在最后加上> *.txt输出到文件。
原作者:suyong_yq