GNU工具链简介

更新时间:2023-11-19 14:28:01 阅读量: 教育文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

GNU工具链简介(全) 2012-01-13 14:24:00

分类:

原文地址:GNU工具链简介(全) 作者:piaoyizu

Mips GNU工具链简介

1 . 总括

本文分三部分来介绍mips的工具链,首先工具链的总括,其次是每个工具链的简介,最后是各工具链的实际应用举例。 我们的项目使用的工具链就如图1所示, 声明,本文所述的内容实乃GNU工具链的九牛之一毛, 日后会陆续更新。

图1

2. 工具链简介

下边就以列表的方式,对每一个工具链的作用进行介绍,在下一章进行实例演示。

mips-linux-gnu-addr2line :

把程序地址转换为文件名和行号。在命令行中给它一个地址和一个可执行文件名,

它就会使用这个可执行文件的调试信息指出在给出的地址上是哪个文件以及行号。

mips-linux-gnu-gcc:

符合ISO标准的C编译器, 这个大家都在用,不再赘述。 mips-linux-gnu-objcopy:

把一种目标文件中的内容复制到另一种类型的目标文件中。 mips-linux-gnu-ar:

建立、修改、提取归档文件。归档文件是包含多个文件内容的一个大文件,其结构

保证了可以恢复原始文件内容。 mips-linux-gnu-gcov:

gcov是一个保险测试工具。当构建一个程序时,gcov会监视一个程序的执行,并且会标识出执行了哪一行源码,哪一行没有执行。 mips-linux-gnu-objdump:

显示一个或者更多目标文件的信息。使用选项来控制其显示的信息。它所显示的信息通常只有编写编译工具的人才感兴趣。 mips-linux-gnu-as:

是 GNU 汇编器,主要用来编译 GNU C 编译器 gcc 输出的汇编文件,它将汇编代码转换成二进制代码,并存放到一个object 文件中,该目标文件将由连接器 ld连接 mips-linux-gnu-gdb:

GNU调试器。允许调试用C\\C++和其他语言编写的应用程序。它的基本运行方式是在shell环境下用命令方式进行调试程序和显示数据。如果加上一些图形前端(如DDD等软件),则可以在一个更方便的图形环境下调试程序

mips-linux-gnu-ranlib:

产生归档文件索引,并将其保存到这个归档文件中。在索引中列出了归档文件各成员所定义的可重分配目标文件。 mips-linux-gnu-c++:

标准的C++编译器。 mips-linux-gnu-gdbtui:

GDB调试器的文本用户界面。 mips-linux-gnu-readelf:

显示 elf 格式可执行文件的信息 mips-linux-gnu-c++filt:

解码 C++ 符号名,连接器使用它来过滤 C++ 和 Java 符号,防止重载函数冲突。

mips-linux-gnu-gprof:

显示程序调用段的各种数据, 包括时间和次数等。 mips-linux-gnu-size:

列出目标文件每一段的大小以及总体的大小。默认情况下,对于每个目标文件或者一个归档文件中的每个模块只产生一行输出。 mips-linux-gnu-cpp:

GNU预处理器,主要用于扩展用户源程序的头文件和宏定义。cpp是GNU C编译器的预处理器。cpp是一个宏指令处理器,GNU C编译器在编译前自动使用cpp对用户程序进行转换。cpp之所以被称为宏指令处理器是因为它允许用户定义宏指令(长结构的简写)。 mips-linux-gnu-ld:

GNU链接器,主要用于确定相对地址,把多个object文件、起始代码段、库等链接起来,并最终形成一个可执行文件。 mips-linux-gnu-strings:

打印某个文件的可打印字符串,这些字符串最少 4 个字符长,也可以使用选项 -n 设置字符串的最小长度。默认情况下,它只打印目标文件初始化和可加载段中的可打印字符;对于其它类型的文件它打印整个文件的可打印字符,这个程序对于了解非文本文件的内容很有帮助。 mips-linux-gnu-g++:

基本符合ISO标准的C++编译器。 mips-linux-gnu-nm:

列出object文件中的符号 mips-linux-gnu-strip:

丢弃目标文件中的全部或者特定符号。

3. 工具使用举例:

对于GNU工具链中的GCC, G++, C++等不再举例。

mips-linux-gnu-ar:

ar 用于建立、修改、提取归档文件,一个归档文件,是包含多个被包含文件的单个文件(也可以认为归档文件是一个库文件)。

被包含的原始文件的内容、权限、时间戳、所有者等属性都保存在归档文件中,并且在提取之后可以还原

主要参数:

r - replace existing or insert new ?le(s) into the archive v - be verbose

生成归档文件的方法:

Mips-linux-gnu-ar –rv libtest.a inputa.o inputb.o

mips-linux-gnu-ranlib:

这又是一个历史遗留问题,在早期版本中,ar只是将.o打包到.a, 而不处理里边的符号表,linker需要一个完整的符号表,所以当时就单独写了一个ranlib来产生linker需要的符号表。 后来产生完整符号表的功能被集成到ar中了,为了兼容,故ranlib还存在,总之以后尽量不要使用ranlib。

生成完整符号表的方法: Mips-linux-gnu-ranlib libtest.a

mips-linux-gnu-nm:

nm 的主要功能是列出目标文件中的符号,这样就可以定位和分析执行程序和目标文件中的符号信息和它的属性。

生成符号前缀的含义:

A:符号的值是绝对值,并且不会被将来的链接所改变

B:符号位于未初始化数据部分(BSS 段)

C:符号是公共的。公共符号是未初始化的数据。在链接时,多个公共符号可能以相同的名字出现。如果符号在其他地方被定义,则该文件中的这个符号会被当作引用来处理 D:符号位于已初始化的数据部分 T:符号位于代码部分

U:符号未被定义 ?:符号类型未知,或者目标文件格式特殊

使用方法:

Mips-linux-gnu-nm –A libtest.a

mips-linux-gnu-gcov:

gcov会监视一个程序的执行,并且会标识出执行了哪一行源码,哪一行没有执行。更进一步,gcov可以标识出某一行源执行的次数,这对于执行配置很有用(程序在哪里花费了大多数的时间)

gcov的参数:

-b,-branch-probabilities向输出文件输出分支频度 -c,-branch-counts 打印分支计数而不是分支频度 -n,-no-output 不创建gcov输出文件 -l,-long-file-names 创建长文件名

-f,-function-summaries 打印每一个函数的概要 -o,-object-directory .bb,.bbg,.da文件存放的目录

Gcov实例(使用的gcc测试):

Mips-linux-gnu-gcc a.c -o a -ftesage -fprofile-arcs ./a 执行a文件//

Mips-linux-gnu-gcov a.c //会自动生成a.c.gcov文件

File 'a.c'

Lines executed:83.33% of 6 a.c:creating 'a.c.gcov'

vi a.c.gcov //就可以看到代码的执行次数

mips-linux-gnu-gcov –b a.c //可以查看gcov的分支执行频率

mips-linux-gnu-c++filt:

大家都知道在C++和JAVA中都有函数重载的机制,编译器在编译相同重载函数时,会使用name mangling机制对函数重新命名,而链接器连接的时候就需要对此进行解析。

C++filt常用参数:

-_ 滤掉符号前的下划线

-j 使用Java语法,默认为C++ -n 不要滤掉符号前的下划线 -p 不要打印函数的参数类型

-s 指定解码方式,不同编译器的编码方式不同。

C++filt 使用实例:

dyron@harris-desktop:~/misc/module/test/tmp$ mips-linux-gnu-nm filt | grep func

080484d8 T _Z4funci 080484c4 T _Z4funcv

dyron@harris-desktop:~/misc/module/test/tmp$ mips-linux-gnu-nm filt | grep func | mips-linux-gnu-c++filt

080484d8 T func(int) 080484c4 T func()

mips-linux-gnu-strip:

strip经常用来去除目标文件中的一些符号表、调试符号表信息,以减小程序的

大小,在rpmbuild包的最后就用到。

值得注意的是,在动态库和静态库时去掉符号表可以给文件瘦身,但再链接时可能编译不过去,所以strip慎用。

Strip使用实例:

dyron@harris-desktop:~/misc/module/test/tmp$ ls -l filt

-rwxr-xr-x 1 dyron dyron 7311 2011-08-31 10:29 filt

dyron@harris-desktop:~/misc/module/test/tmp$ mips-linux-gnu-strip filt

dyron@harris-desktop:~/misc/module/test/tmp$ ls -l filt -rwxr-xr-x 1 dyron dyron 5552 2011-08-31 10:55 filt

mips-linux-gnu-gprof:

打印出程序运行中各个函数消耗的时间,可以帮助程序员找出众多函数中耗时最多的函数。产生程序运行时候的函数调用关系,包括调用次数,可以帮助程序员分析程序的运行流程。

bss_end = .; }

.comment ALIGN(16) : { *(.comment) }

stack_point = __boot_start + 0x00100000; loader_size = __boot_end - __boot_start; setup_size = setup_block_end - setup_block; }

============================= 在SECTIONS命令中的类似于下面的描述结构就是输出段描述: .start ALIGN(4) : { *(.text.start) }

.start 为output section name,ALIGN(4)返回一个基于location counter(.)的4字节对齐的地址值。*(.text.start)是输入段描述,*为通配符,意思是把所有被链接的object文件中 的.text.start段都链接进这个名为.start的输出段。

源文件中所标识的section及其属性实际上就是对输入段的描述,例如.text.start输入段在源文件start.S中的代码如下: .section .text.start .global _start _start : b start

arm-elf-ld -Ttimer.lds -o timer_elf header .o 这里就必须存在一个timer.lds的文件。

对于.lds文件,它定义了整个程序编译之后的连接过程,决定了一个可执行程序的各个段的存储位置。虽然现在我还没怎么用它,但感觉还是挺重要的,有必要了解一下。

先看一下GNU官方网站上对.lds文件形式的完整描述: SECTIONS { ...

secname start BLOCK(align) (NOLOAD) : AT ( ldadr ) { contents } >region :phdr =fill ... }

secname和contents是必须的,其他的都是可选的。下面挑几个常用的看看:

1、secname:段名

2、contents:决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的某段(代码段、数据段等)

3、start:本段连接(运行)的地址,如果没有使用AT(ldadr),本段存储的地址也是start。GNU网站上说start可以用任意一种描述地址的符号来描述。 4、AT(ldadr):定义本段存储(加载)的地址。 /* nand.lds */ SECTIONS {

firtst 0x00000000 : { head.o init.o } second 0x30000000 : AT(4096) { main.o } }

以上,head.o放在0x00000000地址开始处,init.o放在head.o后面,他们的运行地址也是0x00000000,即连接和存储地址相同(没有AT指定);main.o放在4096(0x1000,是AT指定的,存储地址)开始处,但是它的运行地址在0x30000000,运行之前需要从0x1000(加载处)复制到0x30000000(运行处),此过程也就用到了读取Nand flash。

这就是存储地址和连接(运行)地址的不同,称为加载时域和运行时域,可以在.lds连接脚本文件中分别指定。

编写好的.lds文件,在用arm-linux-ld连接命令时带-Tfilename来调用执行,如 arm-linux-ld –Tnand.lds x.o y.o –o xy.o。也用-Ttext参数直接指定连接地址,如 arm-linux-ld –Ttext 0x30000000 x.o y.o –o xy.o。

既然程序有了两种地址,就涉及到一些跳转指令的区别,这里正好写下来,以后万一忘记了也可查看,以前不少东西没记下来现在忘得差不多了。

ARM汇编中,常有两种跳转方法:b跳转指令、ldr指令向PC赋值。 我自己经过归纳如下:

b step1 :b跳转指令是相对跳转,依赖当前PC的值,偏移量是通过该指令本身的bit[23:0]算出来的,这使得使用b指令的程序不依赖于要跳到的代码的位置,只看指令本身。

ldr pc, =step1 :该指令是从内存中的某个位置(step1)读出数据并赋给PC,同样依赖当前PC的值,但是偏移量是那个位置(step1)的连接地址(运行时的地址),所以可以用它实现从Flash到RAM的程序跳转。

此外,有必要回味一下adr伪指令,U-boot中那段relocate代码就是通过adr实现当前程序是在RAM中还是flash中。仍然用我当时的注释 adr r0, _start /* r0是代码的当前位置 */

/* adr伪指令,汇编器自动通过当前PC的值算出 如果执行到_start时PC的值,放到r0中: 当此段在flash中执行时r0 = _start = 0;当此段在RAM中执行时_start = _TEXT_BASE(在board/smdk2410/config.mk中指定的值为0x33F80000,即u-boot在把代码拷贝到RAM中去执行的代码段的开始) */

ldr r1, _TEXT_BASE /* 测试判断是从Flash启动,还是RAM */

/* 此句执行的结果r1始终是0x33FF80000,因为此值是又编译器指定的(ads中设置,或-D设置编译器参数) */

cmp r0, r1 /* 比较r0和r1,调试的时候不要执行重定位 */

下面,结合u-boot.lds看看一个正式的连接脚本文件。这个文件的基本功能还能看明白,虽然上面分析了好多,但其中那些GNU风格的符号还是着实让我感到迷惑。

OUTPUT_FORMAT(\ ;指定输出可执行文件是elf格式,32位ARM指令,小端 OUTPUT_ARCH(arm)

;指定输出可执行文件的平台为ARM ENTRY(_start)

;指定输出可执行文件的起始代码段为_start. SECTIONS {

. = 0x00000000 ; 从0x0位置开始 . = ALIGN(4) ; 代码以4字节对齐 .text : ;指定代码段 {

cpu/arm920t/start.o (.text) ; 代码的第一个代码部分 *(.text) ;其它代码部分 }

. = ALIGN(4)

.rodata : { *(.rodata) } ;指定只读数据段 . = ALIGN(4);

.data : { *(.data) } ;指定读/写数据段 . = ALIGN(4);

.got : { *(.got) } ;指定got段, got段式是uboot自定义的一个段, 非标准段 __u_boot_cmd_start = . ;把__u_boot_cmd_start赋值为当前位置, 即起始位置

.u_boot_cmd : { *(.u_boot_cmd) } ;指定u_boot_cmd段, uboot把所有的uboot命令放在该段. __u_boot_cmd_end = .;把__u_boot_cmd_end赋值为当前位置,即结束位置 . = ALIGN(4);

__bss_start = .; 把__bss_start赋值为当前位置,即bss段的开始位置 .bss : { *(.bss) }; 指定bss段

_end = .; 把_end赋值为当前位置,即bss段的结束位置 }

GNU-ld链接脚本浅析 2011-02-16 14:25:16

分类: 嵌入式

原文地址:http://blogold.chinaunix.net/u/13991/showart_177822.html

本文乃转载, 我在其基础上做了少量修改. 原作者的E-mail是zhanglei@sict.ac.cn. 完成于2005.11.5-2005.11.8

0. Contents 1. 概论 2. 基本概念 3. 脚本格式 4. 简单例子 5. 简单脚本命令 6. 对符号的赋值 7. SECTIONS命令 8. MEMORY命令 9. PHDRS命令 10. VERSION命令 11. 脚本内的表达式 12. 暗含的连接脚本 1. 概论

每一个链接过程都由链接脚本(linker script, 一般以lds作为文件的后缀名)控制. 链接脚本主要用于规定如何把输入文件内的section放入输出文件内, 并控制输出文件内各部分在程序地址空间内的布局. 但你也可以用连接命令做一些其他事情.

连接器有个默认的内置连接脚本, 可用ld --verbose查看. 连接选项-r和-N可以影响默认的连接脚本(如何影响?).

-T选项用以指定自己的链接脚本, 它将代替默认的连接脚本。你也可以使用<暗含的连接脚本>以增加自定义的链接命令.

以下没有特殊说明,连接器指的是静态连接器.

2. 基本概念 链接器把一个或多个输入文件合成一个输出文件. 输入文件: 目标文件或链接脚本文件. 输出文件: 目标文件或可执行文件. 目标文件(包括可执行文件)具有固定的格式, 在UNIX或GNU/Linux平台下, 一般为ELF格式. 若想了解更多, 可参考 UNIX/Linux平台可执行文件格式分析 有时把输入文件内的section称为输入section(input section), 把输出文件内的section称为输出section(output sectin). 目标文件的每个section至少包含两个信息: 名字和大小. 大部分section还包含与它相关联的一块数据, 称为section contents(section内容). 一个section可被标记为“loadable(可加载的)”或“allocatable(可分配的)”. loadable section: 在输出文件运行时, 相应的section内容将被载入进程地址空间中. allocatable section: 内容为空的section可被标记为“可分配的”. 在输出文件运行时, 在进程地址空间中空出大小同section指定大小的部分. 某些情况下, 这块内存必须被置零. 如果一个section不是“可加载的”或“可分配的”, 那么该section通常包含了调试信息. 可用objdump -h命令查看相关信息. 每个“可加载的”或“可分配的”输出section通常包含两个地址: VMA(virtual memory address虚拟内存地址或程序地址空间地址)和LMA(load memory address加载内存地址或进程地址空间地址). 通常VMA和LMA是相同的. 在目标文件中, loadable或allocatable的输出section有两种地址: VMA(virtual Memory Address)和LMA(Load Memory Address). VMA是执行输出文件时section所在的地址, 而LMA是加载输出文件时section所在的地址. 一般而言, 某section的VMA == LMA. 但在嵌入式系统中, 经常存在加载地址和执行地址不同的情况: 比如将输出文件加载到开发板的flash中(由LMA指定), 而在运行时将位于flash中的输出文件复制到SDRAM中(由VMA指定). 可这样来理解VMA和LMA, 假设: (1) .data section对应的VMA地址是0x08050000, 该section内包含了3个32位全局变量, i、j和k, 分别为1,2,3. (2) .text section内包含由\程序片段产生的代码. 连接时指定.data section的VMA为0x08050000, 产生的printf指令是将地址为0x08050004处的4字节内容作为一个整数打印出来。

$ gcc app.c libb.so $ LD_LIBRARY_PATH=. ./app 1

$ nm app | grep true U true@VER1.0 $

显然,连接到了版本号为VER1.0的别名符号true。其中只有一个@表示,该版本不是默认的版本

我的疑问:

版本控制脚本文件中,各版本号节点之间的依赖关系

英文搜索关键字: .symver versioned symbol version a shared library 参考:

info ld, Scripts node

===================== 结束 ==================================

11. 表达式 ----------

表达式的文法与C语言的表达式文法一致,表达式的值都是整型,如果ld的运行主机和生成文件的目标机都是32位,则表达式是32位数据,否则是64位数据。 能够在表达式内使用符号的值,设置符号的值。 下面看六项表达式相关内容,

常表达式:

_fourk_1 = 4K; /* K、M单位 */ _fourk_2 = 4096; /* 整数 */ _fourk_3 = 0x1000; /* 16 进位 */ _fourk_4 = 01000; /* 8 进位 */

1K=1024 1M=1024*1024 符号名:

没有被引号\包围的符号,以字母、下划线或'.'开头,可包含字母、下划线、'.'和'-'。当符号名被引号包围时,符号名可以与关键字相同。如, \

\定位符号'.':

只在SECTIONS命令内有效,代表一个程序地址空间内的地址。

注意:当定位符用在SECTIONS命令的输出section描述内时,它代表的是该section的当前**偏移**,而不是程序地址空间的绝对地址。 先看个例子, SECTIONS { output : {

file1(.text) . = . + 1000; file2(.text) . += 1000; file3(.text) } = 0x1234; }

其中由于对定位符的赋值而产生的空隙由0x1234填充。其他的内容应该容易理解吧。 再看个例子, SECTIONS { . = 0x100 .text: { *(.text) . = 0x200 } . = 0x500 .data: { *(.data) . += 0x600 }

} .text section在程序地址空间的开始位置是0x 表达式的操作符: 与C语言一致。

优先级 结合顺序 操作符 1 left ! - ~ (1) 2 left * / % 3 left + - 4 left >> <<

5 left == != > < <= >= 6 left & 7 left | 8 left && 9 left || 10 right ? :

11 right &= += -= *= /= (2) (1)表示前缀符,(2)表示赋值符。 表达式的计算:

连接器延迟计算大部分表达式的值。

但是,对待与连接过程紧密相关的表达式,连接器会立即计算表达式,如果不能计算则报错。比如,对于section的VMA地址、内存区域块的开始地址和大小,与其相关的表达式应该立即被计算。 例子, SECTIONS {

.text 9+this_isnt_constant : { *(.text) } }

这个例子中,9+this_isnt_constant表达式的值用于设置.text section的VMA地址,因此需要立即运算,但是由于this_isnt_constant变量的值不确定,所以此时连接器无法确立表达式的值,此时连接器会报错。 相对值与绝对值:

在输出section描述内的表达式,连接器取其相对值,相对与该section的开始位置的偏移 在SECTIONS命令内且非输出section描述内的表达式,连接器取其绝对值

通过ABSOLUTE关键字可以将相对值转化成绝对值,即在原来值的基础上加上表达式所在section的VMA值。 例子, SECTIONS {

.data : { *(.data) _edata = ABSOLUTE(.); } }

该例子中,_edata符号的值是.data section的末尾位置(绝对值,在程序地址空间内)。 内建函数:

ABSOLUTE(EXP) :转换成绝对值

ADDR(SECTION) :返回某section的VMA值。

ALIGN(EXP) :返回定位符'.'的修调值,对齐后的值,(. + EXP - 1) & ~(EXP - 1)

BLOCK(EXP) :如同ALIGN(EXP),为了向前兼容。

DEFINED(SYMBOL) :如果符号SYMBOL在全局符号表内,且被定义了,那么返回1,否则返回0。例子, SECTIONS { ... .text : {

begin = DEFINED(begin) ? begin : . ; ... } ... }

LOADADDR(SECTION) :返回三SECTION的LMA MAX(EXP1,EXP2) :返回大者 MIN(EXP1,EXP2) :返回小者

NEXT(EXP) :返回下一个能被使用的地址,该地址是EXP的倍数,类似于ALIGN(EXP)。除非使用了MEMORY命令定义了一些非连续的内存块,否则NEXT(EXP)与ALIGH(EXP)一定相同。

SIZEOF(SECTION) :返回SECTION的大小。当SECTION没有被分配时,即此时SECTION的大小还不能确定时,连接器会报错。

SIZEOF_HEADERS :

sizeof_headers :返回输出文件的文件头大小(还是程序头大小),用以确定第一个section的开始地址(在文件内)。???

12. 暗含的连接脚本

输入文件可以是目标文件,也可以是连接脚本,此时的连接脚本被称为 暗含的连接脚本

如果连接器不认识某个输入文件,那么该文件被当作连接脚本被解析。更进一步,如果发现它的格式又不是连接脚本的格式,那么连接器报错。

一个暗含的连接脚本不会替换默认的连接脚本,仅仅是增加新的连接而已。 一般来说,暗含的连接脚本符号分配命令,或INPUT、GROUP、VERSION命令。

在连接命令行中,每个输入文件的顺序都被固定好了,暗含的连接脚本在连接命令行内占住一个位置,这个位置决定了由该连接脚本指定的输入文件在连接过程中的顺序。

典型的暗含的连接脚本是libc.so文件,在GNU/linux内一般存在/usr/lib目录下。

References 1, gnu ld在线手册

2, 程序的链接和装入及Linux下动态链接的实现

3, UNIX/Linux平台可执行文件格式分析

4, John R. Levine.《Linkers & Loaders》

本文来源:https://www.bwwdw.com/article/vuxv.html

Top