写在前面的话,由于已经学习了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。

后缀名 语言种类 后期操作
.c C源程序 预处理、编译、汇编
.cpp C++源程序 预处理、编译、汇编
.m Objective-C源程序 预处理、编译、汇编
.i 预处理后的C文件 编译、汇编
.ii 预处理后的C++文件 编译、汇编
.s 汇编语言源程序性 汇编
.S 汇编语言源程序 预处理、汇编
.h 预处理文件 通常不出现在命令行上

其他后缀名的文件被传递给连接器(linker),通常包括:

  • o:目标文件(Object file,OBJ文件)
  • a:归档库文件(Archive file)

在编译过程中,除非使用了"-c","-S"或"-E"选项(或者编译错误阻止了完整的过程),否则最后的步骤总是连接。在连接阶段中,所有对应于源程序的.o文件,"-l"选项指定的库文件,无法识别的文件名(包括指定的".o"目标文件和".a"库文件)按命令行中的顺序传递给连接器。

以一个简单的"Hello, world!" C程序为例,它的代码如下,功能为打印"Hello World!"字符串。

/* File: hello.c */
#include <stdio.h>
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 <stdio.h>
#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选项:显示目标文件各个段的头部摘要信息。

--info或-i选项:显示支持的目标文件格式和CPU架构,它们在"-b"、"-m"选项中用到。

--section=name或-j name选项:仅仅显示指定section的信息。

在调试程序时,常用arm-linux-objdump命令来得到汇编代码。使用这两个命令:

将ELF格式的文件转换为反汇编文件:

$ arm-linux-objdump -D elf_file > dis_file

将二进制文件转换为反汇编文件:

$ arm-linux-objdump -D -b binary -m arm bin_file > dis_file

介绍完毕!

交叉编译工具的使用说明相关推荐

  1. 自己动手安装ARM交叉编译工具链

    一,首先说一下在linux中安装软件的特点: 相比windows下安装要稍显复杂,linux中安装一般有以下几种方法: 1.在线安装,譬如ubuntu中使用apt-get install vim 来安 ...

  2. 序列拼接工具Bowtie使用说明

    序列拼接工具Bowtie使用说明 2011-06-08 ~ ADMIN Bowtie是一个超级快速的,较为节省内存的短序列拼接至模板基因组的工具.它在拼接35碱基长度的序列时,可以达到每小时2.5亿次 ...

  3. benchmarksql测试mysql_数据库压力测试工具 -- BenchmarkSQL 使用说明

    关于数据库的压力测试,之前写过3篇Blog: 数据库基准测试(Database Benchmarking) 说明 数据库压力测试工具 -- Hammerdb 使用说明 数据库压力测试工具 -- Swi ...

  4. flutter图标按钮_Flutter开发第一个项目android studio 开发工具的使用说明

    Flutter开发第一个项目android studio 开发工具的使用说明 做个自我介绍 自我介绍还是有必要介绍一下的,毕竟这是网络里,你看不到我,我看不到你,只能通过文字来传递信息,本人做技术8年 ...

  5. arm-linux交叉编译工具链的制作(基于S3C2440)【转】

    本文转载自:http://eric-gao.iteye.com/blog/2160622 制作arm-linux交叉编译工具链一般通过crosstool工具或者crosstool-NG,前者使用方便, ...

  6. linux centos7 使用 crosstool-ng 构建 交叉编译 工具链 即构建各cpu架构平台的gcc编译器

    简介 crosstool-ng,全称是crosstool Next Generation,即下一代crosstool.crosstool是个交叉编译器的制作工具,但是做的不够好,于是有人(Yann E ...

  7. 嵌入式linux编译环境搭建,嵌入式Linux之旅——环境搭建篇之交叉编译工具的安装...

    在正式开始学习裸机程序的编写之前,我们还需要安装一下交叉编译工具.何为交叉编译工具?其实就是在x86的ubuntu主机上编译程序,并且可以运行在arm环境的编译工具.而gcc是本地编译工具,这个在我们 ...

  8. 使用 Android NDK 的交叉编译工具链移植 C/C++ 项目到安卓平台

    什么是 NDK? Android NDK 是一套可以让开发者在安卓应用开发中使用 C/C++ 实现特定模块的工具集,不是所有应用都需要用到,但是正确地使用可以有效提高应用运行效率和安全性. 为什么要在 ...

  9. 树莓派学习笔记——交叉编译工具链

    0.前言 [1]为什么要使用交叉编译     交叉编译工具较快开发的速度.树莓派中已经安装了gcc工具链,可在树莓派中直接编译源代码生成可执行文件.于此同时,PC机上也可使用gcc工具链生成可执行代码 ...

最新文章

  1. 入门指南目录页 -PaddlePaddle 飞桨 入门指南 FAQ合集-深度学习问题
  2. HIVE-ORC表一些知识点
  3. mysql-connector-net不同版本下载
  4. C++ Primer 5th笔记(2)chapter 2变量和基本类型:引用、const
  5. 计算机专业指南考试,电大计算机专业专科期末考试试卷《计算机专业指南》试卷...
  6. android lua sd卡,记Android层执行Lua脚本的一次实践
  7. 对PostgreSQL源代码中的build_jion_rel的理解
  8. Python基础——1基础
  9. 华为发布麒麟990系列芯片,余承东评论友商5G芯片:苹果没有 三星PPT
  10. mono linux 运行机制,linux – Mono如何神奇?
  11. VMware vsphere中虚拟机的基本操作方法
  12. 【方案分享】2021年钟薛高营销策划方案.pptx(附下载链接)
  13. CSS之设置p段落中的文字与页面左侧缩进两个字符!...
  14. PreparedStatement详解
  15. 水系图一般在哪里找得到_水系电池再发Nature,事实力证或将迎来发展的春天!...
  16. 前后端对接及接口管理平台浅析
  17. Android 系统第三方应用系统修改权限及在应用上层显示权限默认打开
  18. DPU网络开发SDK—DPDK(七)
  19. Quick BI和Power BI实测比对
  20. Linux LibTools

热门文章

  1. 信息系统集成-模拟二错题
  2. 18.企业信息化战略与实施
  3. 期末考试:编译原理——如何将控制语句翻译成四元式.
  4. 阿里云服务器DDoS防御方法免受攻击的详细内容
  5. 服务器防御100G是什么意思?够用吗?
  6. Three Character Classic 三字经
  7. 国密 sm2,sm3,sm4 算法纯 JavaScript 实现
  8. 安超云入选《鲲鹏精选解决方案》
  9. 0-1背包问题的一维数组优化解析
  10. Josn字符串处理 explode/from_json/get_json_object的使用--小记补充