历史上的今天
今天是:2025年03月26日(星期三)
2020年03月26日 | 交叉编译工具的使用说明
2020-03-26 来源:eefocus
写在前面的话,由于已经学习了JZ2440V3开发板的裸机程序。想检验下学习成果,所以从今天开始把以前学的知识点在tiny4412开发板上面做个检验。裸机部分学习到把uboot移植完成就结束;然后,学习内核的驱动和其他子系统框架。言归正传,现在开始学习交叉编译工具链的使用。
源文件需要经过编译才能生成可执行文件。在Windows下进行开发时,只需要点几个按钮即可编译,集成开发环境(比如 Visual studio)已经将各种编译工具的使用封装好了。
Linux下也有很优秀的集成开发工具,但是更多的时候是直接以命令方式运行编译工具;即使使用集成开发工具,也需要掌握一些编译选项。
PC机上的编译工具链为gcc、 ld、 objcopy、 objdump等,它们编译出来的程序在x86平台上运行。要编译出能在ARM平台上运行的程序,必须使用交叉编译工具arm-linux-gcc、arm-linux-ld等,下面分别介绍。
1.arm-linux-gcc工具介绍
一个 C/C++文件要经过预处理(preprocessing)、编译(compilation)、汇编(assembly)和连接(linking)等4步才能变成可执行文件,如表1.1所示。在日常交流中通常使用"编译"统称这4个步骤,如果不是特指这4个步骤中的某一个,本书也依惯例使用"编译"这个统称。
1.1.预处理
C/C++源文件中,以"#"开头的命令被称为预处理命令,如包含命令"#include"、宏定义命令"#define"、条件编译命令"#if"、"#ifdef"等。预处理就是将要包含(include)的文件插入原文件中、将宏定义展开、根据条件编译命令选择要使用的代码,最后将这些东西输出到一个".i"文件中等待进一步处理。预处理将用到arm-linux-cpp工具。
1.2.编译
编译就是把 C/C++代码(比如上述的".i"文件)"翻译"成汇编代码,所用到的工具为cc1(它的名字就是 cc1,不是 arm-linux-cc1)。
1.3.汇编
汇编就是将第二步输出的汇编代码翻译成符合一定格式的机器代码,在Linux系统上一般表现为ELF目标文件(OBJ文件),用到的工具为arm-linux-as。"反汇编"是指将机器代码转换为汇编代码,这在调试程序时常常用到。
1.4.连接
连接就是将上步生成的OBJ文件和系统库的OBJ文件、库文件连接起来,最终生成了可以在特定平台运行的可执行文件,用到的工具为arm-linux-ld。
编译器利用这4个步骤中的一个或多个来处理输入文件,源文件的后缀名表示源文件所用的语言,后缀名控制着编译器的缺省动作,如表1.1。

其他后缀名的文件被传递给连接器(linker),通常包括:
o:目标文件(Object file,OBJ文件)
a:归档库文件(Archive file)
在编译过程中,除非使用了"-c","-S"或"-E"选项(或者编译错误阻止了完整的过程),否则最后的步骤总是连接。在连接阶段中,所有对应于源程序的.o文件,"-l"选项指定的库文件,无法识别的文件名(包括指定的".o"目标文件和".a"库文件)按命令行中的顺序传递给连接器。
以一个简单的"Hello, world!" C程序为例,它的代码如下,功能为打印"Hello World!"字符串。
/* File: hello.c */
#include int main(int argc, char *argv[]) { printf("Hello World!n"); return 0; } 使用arm-linux-gcc,只需要一个命令就可以生成可执行文件hello,它包含了上述4个步骤: arm-linux-gcc -o hello hello.c 加上"-v"选项, 即使用"arm-linux-gcc -v -o hello hello.c"命令可以观看编译的细节,下面摘取关键部分: cc1 hello.c -o /tmp/cctETob7.s as -o /tmp/ccvv2KbL.o /tmp/cctETob7.s collect2 -o hello crt1.o crti.o crtbegin.o /tmp/ccvv2KbL.o crtend.o crtn.o 以上3个命令分别对应于编译步骤中的预处理、编译、汇编和连接, ld被collect2调用来连接程序。预处理和编译被放在了一个命令(cc1)中进行的,可以把它再次拆分为以下两步: cpp -o hello.i hello.c cc1 hello.i -o /tmp/cctETob7.s 可以通过各种选项来控制arm-linux-gcc的动作,下面介绍一些常用的选项: 总体选项: -c选项:预处理、编译和汇编源文件,但是不作连接,编译器根据源文件生成OBJ文件。缺省情况下,GCC通过用".o"替换源文件名的后缀".c",".i",".s"等,产生OBJ文件名。可以使用-o选项选择其他名字。GCC忽略-c选项后面任何无法识别的输入文件。 -S选项:编译后即停止,不进行汇编。对于每个输入的非汇编语言文件,输出结果是汇编语言文件。缺省情况下,GCC通过用".s"替换源文件名后缀".c",".i"等等,产生汇编文件名。可以使用-o选项选择其他名字。GCC忽略任何不需要汇编的输入文件。 -E选项:预处理后即停止,不进行编译。预处理后的代码送往标准输出。GCC忽略任何不需要预处理的输入文件。 -o选项:指定输出文件为file。无论是预处理、编译、汇编还是连接,这个选项都可以使用。如果没有使用'-o'选项,默认的输出结果是:可执行文件为'a.out';修改输入文件的名称是'source.suffix',则它的OBJ文件是'source.o',汇编文件是'source.s',而预处理后的C源代码送往标准输出。 -v选项:显示制作GCC工具自身时的配置命令;同时显示编译器驱动程序、预处理器、编译器的版本号。 以一个程序为例,它包含三个文件,下面列出源码: //File: main.c #include #include "sub.h" int main(int argc, char *argv[]) { int i; printf("Main fun!n"); sub_fun(); return 0; } //File: sub.h void sub_fun(void); //File: sub.c void sub_fun(void) { printf("Sub fun!n"); } arm-linux-gcc、arm-linux-ld等工具与gcc、ld等工具的使用方法相似,很多选项是一样的。本节使用gcc、ld等工具进行编译、连接,这样可以在PC上直接看到运行结果。使用上面介绍的选项进行编译,命令如下: $ gcc -c -o main.o main.c $ gcc -c -o sub.o sub.c $ gcc -o test main.o sub.o 其中,main.o、sub.o是经过了预处理、编译、汇编后生成的OBJ文件,它们还没有被连接成可执行文件;最后一步将它们连接成可执行文件test,可以直接运行以下命令: $ ./test Main fun! Sub fun! 现在试试其他选项,以下命令生成的main.s是main.c的汇编语言文件: gcc -S -o main.s main.c 以下命令对main.c进行预处理,并将得到的结果打印出来。里面扩展了所有包含的文件、所有定义的宏。在编写程序时,有时候查找某个宏定义是非常繁琐的事,可以使用'-dM –E'选项来查看。命令如下: $ gcc -E main.c 警告选项: -Wall选项:这个选项基本打开了所有需要注意的警告信息,比如没有指定类型的声明、在声明之前就使用的函数、局部变量除了声明就没再使用等。 上面的main.c文件中,第6行定义的变量i没有被使用,但是使用"gcc –c –o main.o main.c"进行编译时并没有出现提示。 可以加上-Wall选项,例子如下: $ gcc -Wall -c main.c 执行上述命令后,得到如下警告信息: main.c: In function `main': main.c:6: warning: unused variable `i 这个警告虽然对程序没有坏的影响,但是有些警告需要加以关注,比如类型匹配的警告等。 调试选项: -g选项:以操作系统的本地格式(stabs,COFF,XCOFF,或DWARF)产生调试信息,GDB能够使用这些调试信息。在大多数使用stabs格式的系统上,'-g'选项加入只有GDB才使用的额外调试信息。可以使用下面的选项来生成额外的信息:'-gstabs+','-gstabs','-gxcoff+','-gxcoff','-gdwarf+'或'-gdwarf',具体用法请读者参考GCC手册。 优化选项: -O或-O1选项:优化:对于大函数,优化编译的过程将占用稍微多的时间和相当大的内存。不使用"-O"或"-O1"选项的目的是减少编译的开销,使编译结果能够调试、语句是独立的;如果在两条语句之间用断点中止程序,可以对任何变量重新赋值,或者在函数体内把程序计数器指到其他语句,以及从源程序中精确地获取你所期待的结果。 -O2选项:多优化一些。除了涉及空间和速度交换的优化选项,执行几乎所有的优化工作。例如不进行循环展开(loop unrolling)和函数内嵌(inlining)。和'-O'或'-O1'选项比较,这个选项既增加了编译时间,也提高了生成代码的运行效果。 -O3选项:优化的更多。除了打开-O2所做的一切,它还打开了-finline-functions选项。 -O0选项:不优化。 如果指定了多个-O选项,不管带不带数字,生效的是最后一个选项。在一般应用中,经常使用-O2选项,比如对于options程序: $ gcc -O2 -c -o main.o main.c $ gcc -O2 -c -o sub.o sub.c $ gcc -o test main.o sub.o 连接器选项: 下面的选项用于连接OBJ文件,输出可执行文件或库文件。 object-file-name选项:如果某些文件没有特别明确的后缀(a special recognized suffix),GCC就认为他们是OBJ文件或库文件(根据文件内容,连接器能够区分 OBJ 文件和库文件)。如果GCC执行连接操作,这些OBJ文件将成为连接器的输入文件。 比如上面的"gcc -o test main.o sub.o"中,main.o、sub.o就是输入的文件。 -llibrary选项:连接名为library的库文件。连接器在标准搜索目录中寻找这个库文件,库文件的真正名字是'liblibrary.a'。搜索目录除了一些系统标准目录外,还包括用户以'-L'选项指定的路径。 目录选项: 下列选项指定搜索路径,用于查找头文件,库文件,或编译器的某些成员。 -Idir选项:在头文件的搜索路径列表中添加dir目录。 头文件的搜索方法为:如果以"#include<>"包含文件,则只在标准库目录开始搜索(包括使用-Idir选项定义的目录);如以"#include “” "包含文件,则先从用户的工作目录开始搜索,再搜索标准库目录。 1.2.arm-linux-ld工具介绍 arm-linux-ld用于将多个目标文件、库文件连接成可执行文件,它的大多数选项已经在上面介绍过了。 本小节介绍'-T'选项,可以直接使用它来指定代码段、数据段、bss段的起始地址,也可以用来指定一个连接脚本,在连接脚本中进行更复杂的地址设置。 '-T'选项只在连接Bootloader、内核等“没有底层软件支持”的软件;连接运行于操作系统之上的应用程序时,无需指定`-T’ 选项,它们使用默认的方式进行连接。 直接指定代码段、数据段、bss段的起始地址: 格式如下: -Ttext startaddr -Tdata startaddr -Tbss startaddr 其中的'startaddr'分别表示代码段、数据段和bss段的起始地址,它是一个16进制数。示例: arm-linux-ld -Ttext 0x0000000 -g led_on.o -o led_on_elf 它表示代码段的运行地址为0x0000000,由于没有定义数据段、bss段的起始地址,它们被依次放在代码段的后面。 使用连接脚本设置地址: 示例,它的Makefile中有这一句: arm-linux-ld -Ttimer.lds -o timer_elf $^ 其中的'$^'表示"head.o init.o interrupt.o main.o"(为何如此暂时不用管),所以这句代码就变为: arm-linux-ld -Ttimer.lds -o timer_elf head.o init.o interrupt.o main.o 它使用连接脚本timer.lds来设置可执行文件timer_elf的地址信息,timer_elf文件内容如下: SECTIONS { . = 0x30000000; .text : { *(.text) } .rodata ALIGN(4) : {*(.rodata)} .data ALIGN(4) : { *(.data) } .bss ALIGN(4) : { *(.bss) *(COMMON) } } 解析timer_elf文件之前,先讲解连接脚本的格式。连接脚本的基本命令是SECTIONS命令,它描述了输出文件的"映射图":输出文件中各段、各文件怎么放置。一个SECTIONS命令内部包含一个或多个段,段(Section)是连接脚本的基本单元,它表示输入文件中的某部分怎么放置。 完整的连接脚本格式如下,它的核心部分是段(Section): SECTIONS { ... secname start ALIGN(align) (NOLOAD) : AT(ldadr) { contents } >region :phdr =fill ... } secname和contents是必须的,前者用来命名这个段,后者用来确定代码中的什么部分放在这个段中。 start是这个段重定位地址,也称为运行地址。如果代码中有位置相关的指令,程序在运行时,这个段必须放在这个地址上。 ALIGN(align):虽然start指定了运行地址,但是仍可以使用 BLOCK(align)来指定对齐的要求──这个对齐的地址才是真正的运行地址。 (NOLOAD):用来告诉加载器,在运行时不用加载这个段。显然,这个选项只有在有操作系统的情况下才有意义。 AT(ldadr):指定这个段在编译出来的映像文件中的地址──加载地址(load address)。如果不使用这个选项,则加载地址等于运行地址。通过这个选项,可以控制各段分别保存输出文件中不同的位置,便于把文件保存到到单板上:A段放在A处,B段放在B处,运行前再把A、B段分别读出来组装成一个完整的执行程序。 现在,可以明白前面的连接脚本timer.lds的含义了: 第2行表示设置"当前运行地址"为0x30000000。 第3行定义了一个名为".text"的段,它的内容为"*(.text)",表示所有输入文件的代码段。这些代码段被集合在一起,起始运行地址为0x30000000。 第 4 行定义了一个名为".rodata"的段,在输出文件timer_elf中,它紧挨着".text"段存放。其中的"ALIGN(4)"表示起始运行地址为4字节对齐。假设前面".text"段的地址范围是0x30000000~0x300003f1,则".rodata"段的地址是4字节对齐后的0x300003f4。 第5、6行的含义与第4行类似。 1.3.arm-linux-objcopy工具介绍 arm-linux-objcopy被用来拷贝一个目标文件的内容到另一个文件中,可以使用不同于源文件的格式来输出目的文件,即可以进行格式转换。 在本书中,常用arm-linux-objcopy来将ELF格式的可执行文件转换为二进制文件。arm-linux-objcopy的使用格式如下: arm-linux-objcopy [ -F bfdname | --target=bfdname ] [ -I bfdname | --input-target=bfdname ] [ -O bfdname | --output-target= bfdname ] [ -S | --strip-all ] [ -g | --strip-debug ] [ -K symbolname | --keep-symbol= symbolname ] [ -N symbolname | --strip-symbol= symbolname ] [ -L symbolname | --localize-symbol= symbolname ] [ -W symbolname | --weaken-symbol= symbolname ] [ -x | --discard-all ] [ -X | --discard-locals ] [ -b byte | --byte= byte ] [ -i interleave | --interleave= interleave ] [ -R sectionname | --remove-section= sectionname ] [ -p | --preserve-dates ] [ --debugging ] [ --gap-fill= val ] [ --pad-to= address ] [ --set-start= val ] [ --adjust-start= incr ] [ --change-address= incr ] [ --change-section-address= section{=,+,-} val ] [ --change-warnings ] [ --no-change-warnings ] [ --set-section-flags= section= flags ] [ --add-section= sectionname= filename ] [ --change-leading char ] [--remove-leading-char ] [ --weaken ] [ -v | --verbose ] [ -V | --version ] [ --help ] input-file [ outfile ] 下面讲解常用的选项: input-file、outfile选项:参数input-file和outfile分别表示输入目标文件(源目标文件)和输出目标文件(目的目标文件)。如果在命令行中没有明确地指定outfile,那么arm-linux-objcopy将创建一个临时文件来存放目标结果,然后使用input-file的名字来重命名这个临时文件(这时候,原来的input-file将被覆盖)。 -I bfdname或--input-target=bfdname选项:用来指明源文件的格式,bfdname是BFD库中描述的标准格式名。如果不指明源文件格式,arm-linux-objcopy会自己去分析源文件的格式,然后去和BFD中描述的各种格式比较,从而得知源文件的目标格式名。 -O bfdname或--output-target= bfdname选项:使用指定的格式来输出文件,bfdname是BFD库中描述的标准格式名。 -F bfdname或--target= bfdname选项:同时指明源文件、目的文件的格式。将源目标文件中的内容拷贝到目的目标文件的过程中,只进行拷贝不做格式转换,源目标文件是什么格式,目的目标文件就是什么格式。 -R sectionname或--remove-section= sectionname选项:从输出文件中删掉所有名为sectionname的段。这个选项可以多次使用。 -S 或--strip-all选项:不从源文件中拷贝重定位信息和符号信息到目标文件中去。 -g 或--strip-debug选项:不从源文件中拷贝调试符号到目标文件中去。 在编译bootloader、内核时,常用arm-linux-objcopy命令将ELF格式的生成结果转换为二进制文件,比如: $ arm-linux-objcopy -O binary -S elf_file bin_file 1.4.arm-linux-objdump工具介绍: arm-linux-objdump用于显示二进制文件信息,本书中常用来查看反汇编代码。使用格式如下: arm-linux-objdump [-a] [-b bfdname | --target=bfdname] [-C] [--debugging] [-d] [-D] [--disassemble-zeroes] [-EB|-EL|--endian={big|little}] [-f] [-h] [-i|--info] [-j section | --section=section] [-l] [-m machine ] [--prefix-addresses] [-r] [-R] [-s|--full-contents] [-S|--source] [--[no-]show-raw-insn] [--stabs] [-t] [-T] [-x] [--start-address=address] [--stop-address=address] [--adjust-vma=offset] [--version] [--help] objfile... 下面讲解常用的选项: -b bfdname或--target=bfdname选项:指定目标码格式。这不是必须的,arm-linux-objdump能自动识别许多格式。可以使用 "arm-linux-objdump –i"命令查看支持的目标码格式列表。 --disassemble或-d选项:反汇编可执行段(executable sections)。 --disassemble-all或-D选择:与-d 类似,反汇编所有段。 -EB或-EL或--endian={big|little}选项:指定字节序。 --file-headers或-f选项:显示文件的整体头部摘要信息。 --section-headers、--headers或-h选项:显示目标文件各个段的头部摘要信息。
史海拾趣
|
昨天用填充零低格硬盘时选错了硬盘,10秒之后觉得不对强行关机,把低格后的硬盘接在另一台电脑上后,用WinHex查看mbr,不出所料,全部是0.从自己机器硬盘复制前446个字节后,后面64位分区表搞不定了。 我用数据修复软 ...… 查看全部问答> |
|
刚才做实验发现一个大问题,使用ST的固件库 执行如下程序 while(1){ GPIO_SetBits(GPIOB,GPIO_Pin_11); GPIO_ResetBits(GPIOB,GPIO_Pin_11); }用示波器看完整的波形周期竟然 ...… 查看全部问答> |
|
美国,达拉斯-2009 年 5月14日,德州仪器公司(NYSE:TXN)宣布,为了扩展 MCU产品线,将收购Luminary Micro (全球领先的基于ARM Cortex-M3的32位MCU的供应商) 。Luminary Micro公司旗下的Stellari ...… 查看全部问答> |
|
FLASH是SST39VF400A,原理图用的和CCS文档中的提供的,DSP是TMS320C6701,程序跑起来,运行到写数据就死了,不知道怎么回事? 代码如下: void main() { int f; f = Check_SST_39VF400A(); &nbs ...… 查看全部问答> |
|
我自己买的LPC2148的芯片,自己焊接的电路,就是焊上晶振,电容,电阻后,做成一个最小系统;现在jlink可以连接,也可以烧写HEX到2148内,但是程序运行不正常?串口出来的数据不是程序里面预设的;就是一些有规律的乱码,而且循环的乱码。。   ...… 查看全部问答> |




