GCC在最初指GNU C Compiler,作为GNU计划中相当重要的一环,在GNU发展初期只支持C语言,这是被当时的编程语言发展限制的。随后编程语言爆发式增加,也极大地促进了GCC的扩展,使得GCC编译器可以使用C++、Fortran、Pascal、Java等语言,也可以针对不同处理器架构如x86、ARM、MIPS、PowerPC等,编译汇编语言程序。此时的GCC不再是单一的C语言、适用于x86体系架构的编译工具链,于是又改名为GUN Compiler Collection,意为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
,新增的参数有:
-mcpu=
-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)由以下几部分组成:
-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标准。
-Wal
l表示编译过程中所有遇到的警告全部输出,但不会强制停止编译并报错。
-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
输出到文件。
END