历史上的今天
返回首页

历史上的今天

今天是: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。

image.png?imageView2/2/w/550

其他后缀名的文件被传递给连接器(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选项:显示目标文件各个段的头部摘要信息。

推荐阅读

史海拾趣

上海如韵(CONSONANCE)公司的发展小趣事

随着市场需求的不断变化,如韵意识到技术创新是企业持续发展的关键。因此,公司投入大量资源进行产品研发和技术升级。其中,一次重要的技术突破让如韵在行业内声名鹊起。

当时,市场上的某种电子元器件存在性能不稳定的问题,给许多电子设备制造商带来了困扰。如韵的研发团队经过深入研究,成功开发出了一种新型电子元器件,不仅性能稳定可靠,而且成本更低。这一创新产品迅速占领了市场份额,成为公司的明星产品,也为如韵带来了丰厚的经济回报。

DFI公司的发展小趣事

随着市场竞争的加剧,DFI意识到单一产品的竞争力有限。因此,公司开始寻求垂直整合的机会,与罗升等公司合作,共同开发智能制造的全面性解决方案。这一举措不仅使DFI能够提供更完整的产品线,还增强了其在行业中的竞争力。通过与合作伙伴的紧密合作,DFI成功推出了一系列具有竞争力的智能制造解决方案,赢得了市场的广泛认可。

AiT Semiconductor Inc公司的发展小趣事

在半导体行业,技术创新是企业持续发展的关键。AiT公司深知这一点,因此始终将创新作为公司的核心竞争力。他们投入大量资金和资源用于研发,不断推出具有创新性和领先性的半导体产品。同时,公司还积极与高校、科研机构等合作,共同开展技术研发和人才培养工作。这些创新举措使得AiT公司在行业中始终保持领先地位。

Corsair Electrical Connectors Inc公司的发展小趣事

Corsair Electrical Connectors Inc公司自创立之初,便专注于电气连接器的制造。公司创始人凭借其深厚的行业经验和敏锐的市场洞察力,准确把握了连接器在电子行业中的重要地位,并决定以此为切入点,打造一家专业的连接器制造商。在创立初期,Corsair面临着资金短缺、技术落后等多重困难,但创始人凭借坚定的信念和不懈的努力,带领公司逐步走上了正轨。

Geo Semiconductor Inc公司的发展小趣事
增加CPU温度监控电路,确保在高速运算时CPU温度不会过高,防止因过热导致的系统崩溃或损坏。
Danaher Corporation公司的发展小趣事

丹纳赫集团起源于1984年,由Steven Rales和Mitchell Rales兄弟二人创立。他们的愿景是建立一个致力于持续改善并提升客户满意度的制造公司。名字“Dana”源自古老的凯尔特语,寓意奔腾不息的精神和不断改善的品质。起初,丹纳赫集团并未直接涉足电子行业,但随着公司的发展,逐渐将业务范围扩展至包括电子设备和技术的领域。

问答坊 | AI 解惑

美国人设计的555电路分析

这是美国人设计的555电路,请555电路高手分析当合上AN 开关后(不松开),线圈KA得电情况 另外C945在555的复位端(4端)是起什么作用…

查看全部问答>

低格10秒后硬盘的mbr手写恢复问题 求助 小弟叩首

    昨天用填充零低格硬盘时选错了硬盘,10秒之后觉得不对强行关机,把低格后的硬盘接在另一台电脑上后,用WinHex查看mbr,不出所料,全部是0.从自己机器硬盘复制前446个字节后,后面64位分区表搞不定了。     我用数据修复软 ...…

查看全部问答>

如何在Pocket PC上安装应用程序?

我用VS2005写了一个小程序,已经成功运行部署到Pocket PC上,但是不知道怎么才能把应用程序安装到Pocket PC上?请指教。…

查看全部问答>

STM32的运行速度到底是多少?

刚才做实验发现一个大问题,使用ST的固件库 执行如下程序 while(1){    GPIO_SetBits(GPIOB,GPIO_Pin_11);    GPIO_ResetBits(GPIOB,GPIO_Pin_11); }用示波器看完整的波形周期竟然 ...…

查看全部问答>

TI收购Luminary!!!

美国,达拉斯-2009 年 5月14日,德州仪器公司(NYSE:TXN)宣布,为了扩展 MCU产品线,将收购Luminary Micro (全球领先的基于ARM Cortex-M3的32位MCU的供应商) 。Luminary Micro公司旗下的Stellari ...…

查看全部问答>

ccs运行时停留在启动界面了,是怎么回事呢

这个故障的原因比较多,你用什么芯片呀…

查看全部问答>

FLASH写不进去,怎么回事?

FLASH是SST39VF400A,原理图用的和CCS文档中的提供的,DSP是TMS320C6701,程序跑起来,运行到写数据就死了,不知道怎么回事? 代码如下: void main() {     int f;         f = Check_SST_39VF400A();   &nbs ...…

查看全部问答>

3517制作双分区SD卡

         在windows下格式化SD卡为FAT32格式。                      卸载SD卡 使用命令 mount 查看S ...…

查看全部问答>

多功能电子时钟

多功能电子时钟…

查看全部问答>

挑战性的问题,都进来捧场下

我自己买的LPC2148的芯片,自己焊接的电路,就是焊上晶振,电容,电阻后,做成一个最小系统;现在jlink可以连接,也可以烧写HEX到2148内,但是程序运行不正常?串口出来的数据不是程序里面预设的;就是一些有规律的乱码,而且循环的乱码。。   ...…

查看全部问答>