链接脚本文件语法详解

合集下载

gcc链接脚本学习

gcc链接脚本学习

GCC的连接脚本学习笔记来源: ChinaUnix博客日期:2009.04.29 14:26(共有条评论) 我要评论连接脚本将我整整蒙了1天零一个上午,做了很多实验,看了人家不少例子代码勉强能驾驭了,让linker按照我想要的来处理,做个笔记。

1,什么叫输入段,什么叫输出段不知道怎么回事,我对GCC系列的输入和输出两个单词总是进入思维死角,很简单就是input section 和output section,这里不是说翻译的问题,我觉得是一种思考的方式的问题。

我的问题就是:既然叫输入端,那输入什么?同理,输出的是什么?不知道其他人不会不理解这个问题,我自己的话是理解了不少时间了-v-所谓的输出段,是指生成的文件,例如elf 中的每个段所谓的输入段,是指连接的时候提供LD的所有目标文件(OBJ)中的段。

2,lma 和vmalma = load memory addressvma = vitual memory address如果有研究过ADS的估计有印象,那里有个RO BASE 和RW BASE 和ZI BASE,也就是说,lma 是装载地址,vma 是运行地址,想搞清楚这两个问题,可以阅读一下《ARM学习报告(杜云海)》作者写的很好,将这个问题分析的很透澈。

lma 和vma只是GCC的叫法而已,其实原理是一样的。

3,两个基本架构OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm","elf32-littlearm")OUTPUT_ARCH(arm)一句话,照抄......因为我们没有修改的余地,都是系统默认的关键字。

第一句指示系统可以有生成两种格式,默认是elf32-arm,端格式是little endian4,ENTRY(__ENTRY)指定入口点,LD的手册说,ENTRY POINT 就是程序第一条执行的指令,但是,说老实话,我并不理解,因为这里跟我的理解矛盾了,首先,通常情况,系统需要一个初始化的STARTUP.S文件来初始化硬件,也就是bootloader的第一阶段了。

ARM-GCC-LD脚本

ARM-GCC-LD脚本

ARM-GCC-LD脚本从以前的经验,链接脚本是嵌⼊式开发,单⽚机开发相当重要的⼀个东西。

它完成的⼯作是做PC机软件的同志们不⽤关⼼的,但是也是很复杂的⼀项⼯作。

总结来看链接脚本要告诉连接器1:输出什么2:输⼊是什么,那么obj⽂件3:要⽤什么库,库放在什么地⽅4:内存分布地址5:提供启动代码⼀些全局地址变量⼀般来说链接脚本需要搞清楚这⼏样事情后才能编写,那arm-gcc-ld的脚本也⼀定要实现这些功能。

对于⼤多数的链接器来说,对于简单的项⽬不需要脚本,只是使⽤命令参数就可以完成了。

MEMORY:它是⽤来补充SECTIONS命令的,⽤来描述⽬标CPU中可⽤的内存区域。

它是可选的,如果没有这个命令,LD会认为SECTIONS描述的相邻的内存块之间有⾜够可⽤的内存。

其实很容易理解但是却很少⽤(我没⽤过,嘿嘿),在SECTIONS中每个段的分布都没有考虑ARM能够寻址的地址中,ROM,RAM,FLASH是不是连续的。

如果不是连续的怎么办?MEMORY就是设置各个区的起始位置,⼤⼩,属性的命令,在⼀个脚本中只能有⼀个。

举⼀个例⼦:如果你的板⼦有两段存储,⽽且很遗憾的是不是连续的,⼀段是从0x0开始,⼤⼩为256K,另⼀段是从0x40000000开始的⼤⼩为4M,你可以在脚本中写⼊如下的代码来描述你的板⼦的内存信息。

1 MEMORY2 {3 rom (rx) : ORIGIN = 0, LENGTH = 256K4 ram (!rx) : org = 0x40000000, l = 4M5 }很显然下⾯的⼀句⽤了简略标签,这并不重要,重要的是怎样使⽤它,不过在那之前还是想再仔细研究下MEMORY命令的细节。

MEMORY命令的语法是:MEMORY{name (attr) : ORIGIN = origin, LENGTH = len...}name:⼀个⽤户定义的名字,Linker将在内部使⽤它,所以别把它和SECTIONS⾥⽤到的⽂件名,段名等搞重复了,它要求是独⼀⽆⼆的。

vbscript语法基础

vbscript语法基础

vbscript语法基础VBScript是一种用于Windows系统的脚本语言,它是Visual Basic 的一个子集,主要用于开发简单的应用程序和自动化任务。

本文将介绍VBScript语法基础,包括变量、数据类型、运算符、条件语句、循环语句等内容。

VBScript中的变量可以用来存储数据。

在声明变量时,需要使用关键字"Dim",后面跟上变量名。

例如,"Dim name"就声明了一个名为name的变量。

变量可以存储不同的数据类型,包括字符串、整数、浮点数等。

VBScript中的字符串是用双引号括起来的,例如"Hello World"。

可以使用"+"运算符来连接字符串,例如"Hello" + " " + "World"的结果是"Hello World"。

此外,还可以使用字符串函数来操作字符串,例如"Len"函数可以返回字符串的长度。

VBScript中的整数和浮点数可以使用基本的数学运算符进行计算,包括加减乘除和取余等。

例如,"1 + 2"的结果是3,"5 / 2"的结果是 2.5。

除法运算中,如果两个操作数都是整数,则结果也是整数。

条件语句是用来根据条件选择不同的执行路径的。

VBScript中的条件语句包括"if-then"语句和"if-then-else"语句。

"if-then"语句用来判断一个条件是否为真,如果为真,则执行相应的代码块。

例如,"if x > 0 then"就是一个简单的条件语句,如果变量x的值大于0,则执行if语句块中的代码。

循环语句是用来重复执行一段代码块的。

VBScript中的循环语句包括"do-while"循环和"for-next"循环。

jinjia语法

jinjia语法

jinjia语法Jinja语法是一种基于Python的模板引擎语法,用于在网页和应用程序中生成动态内容。

它具有简洁、灵活和易于学习的特点,因此在Web开发中得到了广泛应用。

一、Jinja语法简介Jinja语法使用{{ }}和{% %}作为标记,用于插入变量和执行控制流语句。

其中,{{ }}用于输出变量的值,{% %}用于控制流语句的执行。

二、变量插入Jinja语法允许在模板中插入变量,使得动态内容可以根据实际情况进行显示。

例如,可以使用{{ name }}插入名字变量,使用{{ age }}插入年龄变量。

三、控制流语句Jinja语法中的控制流语句可以根据条件或循环来控制模板的输出。

常见的控制流语句包括if语句、for循环和宏定义等。

1. if语句if语句用于根据条件判断是否执行某段代码。

例如,可以使用{% if condition %}来判断condition是否为真,如果为真,则执行相应的代码块。

2. for循环for循环用于遍历一个可迭代对象,并对其中的每个元素执行相同的操作。

例如,可以使用{% for item in iterable %}来遍历iterable中的每个元素,并执行相应的代码块。

3. 宏定义宏定义可以将一段代码片段封装为一个可重复使用的模板块。

例如,可以使用{% macro name(arg1, arg2) %}来定义一个宏,然后在模板中使用{{ name(arg1, arg2) }}来调用该宏。

四、模板继承Jinja语法支持模板继承,可以将公共的部分提取到一个父模板中,然后在子模板中通过{% extends 'parent.html' %}来继承父模板,并使用{% block content %}来替换父模板中的相应内容。

五、过滤器Jinja语法提供了丰富的过滤器,用于对变量进行处理和转换。

例如,可以使用{{ text|length }}来获取text变量的长度,使用{{ text|upper }}来将text变量转换为大写。

markdown url和file的语法

markdown url和file的语法

markdown url和file的语法
Markdown的URL和文件语法如下:
1. URL语法:使用方括号[],在括号内输入链接地址。

例如,要插入一个指向百度的链接,可以这样写:
```
[点击跳转到百度](
```
如果需要在链接地址后面添加提示文字,可以在方括号后面直接添加,文字用引号包裹。

例如:
```
[点击访问GitHub]( "GitHub官方网站")
```
2. 文件语法:Markdown本身不提供直接插入文件的功能,但可以在链接地址中指定文件下载地址,使用方括号[],在括号内输入链接地址。

例如:
```
[点击下载文件](/path/to/)
```
注意,这里的链接地址可以是本地路径或网络链接。

如果需要添加提示文字,可以在方括号后面直接添加,文字用引号包裹。

例如:
```
[点击下载报告](/path/to/ "2022年第一季度销售报告")
```。

linker script语法

linker script语法

Linker script是用于描述输出文件内存布局的脚本语言。

它的语法如下:1. 关键字:PHDRS、FILEHDR、AT、FLAGS等都是关键字。

这些关键字用于描述不同的程序头和属性。

2. 标识符:标识符用于引用程序头或其他元素。

每个程序头必须有一个唯一的标识符,并且标识符不会被放到输出文件中。

3. 符号名:符号名用于引用程序中的函数或变量。

它们可以是任意有效的标识符。

4. 属性值:属性值可以是数字、字符串或符号名。

它们用于描述程序头的属性,例如地址、大小等。

5. 语句分隔符:语句之间需要用分号(;)分隔。

6. 注释:注释以“/”开头,以“/”结尾,用于说明代码的含义和作用。

注释不会被编译器执行。

以下是一个简单的Linker script示例:```makefilePHDRS {text : AT (load_address) : TYPE = loadable : FILEHDR (512) : FLAGS (0x0) ;data : AT (load_address + 0x1000) : TYPE = loadable : FILEHDR (512) : FLAGS (0x0) ;bss : AT (load_address + 0x2000) : TYPE = loadable : FILEHDR (512) : FLAGS (0x0) ;}SECTIONS {.text : { *(.text) } : text.data : { *(.data) } : data.bss : { *(.bss) } : bss ;}```这个示例中,定义了三个程序头:text、data和bss。

每个程序头都有一个地址属性,用于指定输出文件中的加载地址。

类型属性指定了每个程序头的类型,loadable表示可加载的类型。

FLAGS属性用于描述程序头的其他属性,例如是否可写或可执行。

在SECTIONS命令中,将代码段(.text)、初始化数据段(.data)和未初始化数据段(.bss)映射到相应的程序头上。

连接脚本分析

连接脚本分析

链接脚本分析(2)(2009-01-21 14:22:22)转载标签:杂谈5. 简单脚本命令--------------------------------------------------------------------------------- 1 -ENTRY(SYMBOL) : 将符号SYMBOL的值设置成入口地址.入口地址(entry point): 进程执行的第一条用户空间的指令在进程地址空间的地址)ld有多种方法设置进程入口地址, 按一下顺序: (编号越前, 优先级越高)1, ld命令行的-e选项2, 连接脚本的ENTRY(SYMBOL)命令3, 如果定义了start符号, 使用start符号值4, 如果存在.text section, 使用.text section的第一字节的位置值5, 使用值0- 2 -INCLUDE filename : 包含其他名为filename的链接脚本相当于c程序内的的#include指令, 用以包含另一个链接脚本.脚本搜索路径由-L选项指定. INCLUDE指令可以嵌套使用, 最大深度为10. 即: 文件1内INCLUDE文件2, 文件2内INCLUDE文件3... , 文件10内INCLUDE文件11. 那么文件11内不能再出现 INCLUDE指令了.- 3 -INPUT(files): 将括号内的文件做为链接过程的输入文件ld首先在当前目录下寻找该文件, 如果没找到, 则在由-L指定的搜索路径下搜索. file可以为 -lfile形式,就象命令行的-l选项一样. 如果该命令出现在暗含的脚本内, 则该命令内的file在链接过程中的顺序由该暗含的脚本在命令行内的顺序决定.- 4 -GROUP(files) : 指定需要重复搜索符号定义的多个输入文件file必须是库文件, 且file文件作为一组被ld重复扫描,直到不在有新的未定义的引用出现.- 5 -OUTPUT(FILENAME) : 定义输出文件的名字同ld的-o选项, 不过-o选项的优先级更高. 所以它可以用来定义默认的输出文件名. 如a.out- 6 -SEARCH_DIR(PATH) :定义搜索路径,同ld的-L选项, 不过由-L指定的路径要比它定义的优先被搜索.- 7 -STARTUP(filename) : 指定filename为第一个输入文件在链接过程中, 每个输入文件是有顺序的. 此命令设置文件filename为第一个输入文件.- 8 -OUTPUT_FORMAT(BFDNAME) : 设置输出文件使用的BFD格式同ld选项-o format BFDNAME, 不过ld选项优先级更高.- 9 -OUTPUT_FORMAT(DEFAULT,BIG,LITTLE) : 定义三种输出文件的格式(大小端)若有命令行选项-EB, 则使用第2个BFD格式; 若有命令行选项-EL,则使用第3个BFD格式.否则默认选第一个BFD格式.TARGET(BFDNAME):设置输入文件的BFD格式同ld选项-b BFDNAME. 若使用了TARGET命令, 但未使用OUTPUT_FORMAT命令, 则最用一个TARGET命令设置的BFD格式将被作为输出文件的BFD格式.另外还有一些:ASSERT(EXP, MESSAGE):如果EXP不为真,终止连接过程EXTERN(SYMBOL SYMBOL ...):在输出文件中增加未定义的符号,如同连接器选项-uFORCE_COMMON_ALLOCATION:为common symbol(通用符号)分配空间,即使用了-r连接选项也为其分配NOCROSSREFS(SECTION SECTION ...):检查列出的输出section,如果发现他们之间有相互引用,则报错.对于某些系统,特别是内存较紧张的嵌入式系统,某些section是不能同时存在内存中的,所以他们之间不能相互引用.OUTPUT_ARCH(BFDARCH):设置输出文件的machine architecture(体系结构),BFDARCH为被BFD库使用的名字之一.可以用命令objdump -f查看.可通过 man -S 1 ld查看ld的联机帮助, 里面也包括了对这些命令的介绍. 6. 对符号的赋值--------------------------------------------------------------------------------在目标文件内定义的符号可以在链接脚本内被赋值. (注意和C语言中赋值的不同!) 此时该符号被定义为全局的. 每个符号都对应了一个地址, 此处的赋值是更改这个符号对应的地址.e.g. 通过下面的程序查看变量a的地址:#include <stdio.h>int a = 100;int main(void){printf( "&a=0x%p ", &a );return 0;}a = 3;$ gcc -Wall -o a-without-lds a.c&a = 0x8049598$ gcc -Wall -o a-with-lds a.c a.lds&a = 0x3注意: 对符号的赋值只对全局变量起作用!一些简单的赋值语句能使用任何c语言内的赋值操作:SYMBOL = EXPRESSION ;SYMBOL += EXPRESSION ;SYMBOL -= EXPRESSION ;SYMBOL *= EXPRESSION ;SYMBOL /= EXPRESSION ;SYMBOL <<= EXPRESSION ;SYMBOL >>= EXPRESSION ;SYMBOL &= EXPRESSION ;SYMBOL |= EXPRESSION ;除了第一类表达式外, 使用其他表达式需要SYMBOL被定义于某目标文件.. 是一个特殊的符号,它是定位器,一个位置指针,指向程序地址空间内的某位置(或某section内的偏移,如果它在SECTIONS命令内的某section描述内),该符号只能在SECTIONS命令内使用.注意:赋值语句包含4个语法元素:符号名、操作符、表达式、分号;一个也不能少.被赋值后,符号所属的section被设值为表达式EXPRESSION所属的SECTION(参看11. 脚本内的表达式)赋值语句可以出现在连接脚本的三处地方:SECTIONS命令内,SECTIONS命令内的section描述内和全局位置;如下,floating_point = 0;SECTIONS{.text :{*(.text)_etext = .;}_bdata = (. + 3) & ~ 4;.data : { *(.data) }}PROVIDE关键字该关键字用于定义这类符号:在目标文件内被引用,但没有在任何目标文件内被定义的符号.例子:SECTIONS{.text :{*(.text)_etext = .;PROVIDE(etext = .);}}当目标文件内引用了etext符号,确没有定义它时,etext符号对应的地址被定义为.text section之后的第一个字节的地址.7. SECTIONS命令--------------------------------------------------------------------------------SECTIONS命令告诉ld如何把输入文件的sections映射到输出文件的各个section: 如何将输入section合为输出section; 如何把输出section放入程序地址空间(VMA)和进程地址空间(LMA).该命令格式如下:SECTIONS{SECTIONS-COMMANDSECTIONS-COMMAND...}SECTION-COMMAND有四种:(1) ENTRY命令(2) 符号赋值语句(3) 一个输出section的描述(output section description)(4) 一个section叠加描述(overlay description)如果整个连接脚本内没有SECTIONS命令, 那么ld将所有同名输入section合成为一个输出section内, 各输入section的顺序为它们被连接器发现的顺序. 如果某输入section没有在SECTIONS命令中提到, 那么该section将被直接拷贝成输出section.输出section描述输出section描述具有如下格式:SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]{OUTPUT-SECTION-COMMANDOUTPUT-SECTION-COMMAND...} [>REGION] [AT>LMA_REGION] [:PHDR :PHDR ...] [=FILLEXP][ ]内的内容为可选选项, 一般不需要.SECTION:section名字SECTION左右的空白、圆括号、冒号是必须的,换行符和其他空格是可选的.每个OUTPUT-SECTION-COMMAND为以下四种之一,符号赋值语句一个输入section描述直接包含的数据值一个特殊的输出section关键字输出section名字(SECTION):输出section名字必须符合输出文件格式要求,比如:a.out格式的文件只允许存在.text、.data和.bss section名.而有的格式只允许存在数字名字,那么此时应该用引号将所有名字内的数字组合在一起;另外,还有一些格式允许任何序列的字符存在于 section名字内,此时如果名字内包含特殊字符(比如空格、逗号等),那么需要用引号将其组合在一起.输出section地址(ADDRESS):ADDRESS 是一个表达式,它的值用于设置VMA.如果没有该选项且有REGION选项,那么连接器将根据REGION设置VMA;如果也没有 REGION选项,那么连接器将根据定位符号‘.’的值设置该section的VMA,将定位符号的值调整到满足输出section对齐要求后的值,输出 section的对齐要求为:该输出section描述内用到的所有输入section的对齐要求中最严格的.例子:.text . : { *(.text) }和.text : { *(.text) }这两个描述是截然不同的,第一个将.text section的VMA设置为定位符号的值,而第二个则是设置成定位符号的修调值,满足对齐要求后的.ADDRESS可以是一个任意表达式,比如ALIGN(0x10)这将把该section的VMA设置成定位符号的修调值,满足16字节对齐后的.注意:设置ADDRESS值,将更改定位符号的值.输入section描述:最常见的输出section描述命令是输入section描述.输入section描述是最基本的连接脚本描述.输入section描述基础:基本语法:FILENAME([EXCLUDE_FILE (FILENAME1 FILENAME2 ...) SECTION1 SECTION2 ...)FILENAME文件名,可以是一个特定的文件的名字,也可以是一个字符串模式. SECTION名字,可以是一个特定的section名字,也可以是一个字符串模式例子是最能说明问题的,*(.text) :表示所有输入文件的.text section(*(EXCLUDE_FILE (*crtend.o *otherfile.o) .ctors)) :表示除crtend.o、otherfile.o文件外的所有输入文件的.ctors section.data.o(.data) :表示data.o文件的.data sectiondata.o :表示data.o文件的所有section*(.text .data) :表示所有文件的.text section和.data section,顺序是:第一个文件的.text section,第一个文件的.data section,第二个文件的.text section,第二个文件的.data section,...*(.text) *(.data) :表示所有文件的.text section和.data section,顺序是:第一个文件的.text section,第二个文件的.text section,...,最后一个文件的.text section,第一个文件的.data section,第二个文件的.data section,...,最后一个文件的.data section下面看连接器是如何找到对应的文件的.当FILENAME是一个特定的文件名时,连接器会查看它是否在连接命令行内出现或在INPUT命令中出现.当FILENAME是一个字符串模式时,连接器仅仅只查看它是否在连接命令行内出现.注意:如果连接器发现某文件在INPUT命令内出现,那么它会在-L指定的路径内搜寻该文件.字符串模式内可存在以下通配符:* :表示任意多个字符? :表示任意一个字符[CHARS] :表示任意一个CHARS内的字符,可用-号表示范围,如:a-z:表示引用下一个紧跟的字符在文件名内,通配符不匹配文件夹分隔符/,但当字符串模式仅包含通配符*时除外.任何一个文件的任意section只能在SECTIONS命令内出现一次.看如下例子,SECTIONS {.data : { *(.data) }.data1 : { data.o(.data) }}data.o 文件的.data section在第一个OUTPUT-SECTION-COMMAND命令内被使用了,那么在第二个OUTPUT-SECTION-COMMAND命令内将不会再被使用,也就是说即使连接器不报错,输出文件的.data1 section的内容也是空的.再次强调:连接器依次扫描每个OUTPUT-SECTION-COMMAND命令内的文件名,任何一个文件的任何一个section都只能使用一次.读者可以用-M连接命令选项来产生一个map文件,它包含了所有输入section到输出section的组合信息.再看个例子,SECTIONS {.text : { *(.text) }.DATA : { [A-Z]*(.data) }.data : { *(.data) }.bss : { *(.bss) }}这个例子中说明,所有文件的输入.text section组成输出.text section;所有以大写字母开头的文件的.data section组成输出.DATA section,其他文件的.data section组成输出.data section;所有文件的输入.bss section组成输出.bss section.可以用SORT()关键字对满足字符串模式的所有名字进行递增排序,如SORT(.text*).通用符号(common symbol)的输入section:在许多目标文件格式中,通用符号并没有占用一个section.连接器认为:输入文件的所有通用符号在名为COMMON的section内.例子,.bss { *(.bss) *(COMMON) }这个例子中将所有输入文件的所有通用符号放入输出.bss section内.可以看到COMMOM section的使用方法跟其他section的使用方法是一样的.有些目标文件格式把通用符号分成几类.例如,在MIPS elf目标文件格式中,把通用符号分成standard common symbols(标准通用符号)和small common symbols(微通用符号,不知道这么译对不对?),此时连接器认为所有standard common symbols在COMMON section内,而small common symbols在.scommon section内.在一些以前的连接脚本内可以看见[COMMON],相当于*(COMMON),不建议继续使用这种陈旧的方式.输入section和垃圾回收:在连接命令行内使用了选项--gc-sections后,连接器可能将某些它认为没用的section过滤掉,此时就有必要强制连接器保留一些特定的 section,可用KEEP()关键字达此目的.如KEEP(*(.text))或KEEP(SORT(*)(.text))最后看个简单的输入section相关例子:SECTIONS {outputa 0x10000 :{all.ofoo.o (.input1)}outputb :{foo.o (.input2)foo1.o (.input1)}outputc :{*(.input1)*(.input2)}}本例中,将all.o文件的所有section和foo.o文件的所有(一个文件内可以有多个同名section).input1 section依次放入输出outputa section内,该section的VMA是0x10000;将foo.o文件的所有.input2 section和foo1.o文件的所有.input1 section依次放入输出outputb section内,该section的VMA是当前定位器符号的修调值(对齐后);将其他文件(非all.o、foo.o、foo1.o)文件的. input1 section和.input2 section放入输出outputc section内. 在输出section存放数据命令:能够显示地在输出section内填入你想要填入的信息(这样是不是可以自己通过连接脚本写程序?当然是简单的程序).BYTE(EXPRESSION) 1 字节SHORT(EXPRESSION) 2 字节LOGN(EXPRESSION) 4 字节QUAD(EXPRESSION) 8 字节SQUAD(EXPRESSION) 64位处理器的代码时,8 字节输出文件的字节顺序big endianness 或little endianness,可以由输出目标文件的格式决定;如果输出目标文件的格式不能决定字节顺序,那么字节顺序与第一个输入文件的字节顺序相同.如:BYTE(1)、LANG(addr).注意,这些命令只能放在输出section描述内,其他地方不行.错误:SECTIONS { .text : { *(.text) } LONG(1) .data : { *(.data) } } 正确:SECTIONS { .text : { *(.text) LONG(1) } .data : { *(.data) } } 在当前输出section内可能存在未描述的存储区域(比如由于对齐造成的空隙),可以用FILL(EXPRESSION)命令决定这些存储区域的内容, EXPRESSION的前两字节有效,这两字节在必要时可以重复被使用以填充这类存储区域.如FILE(0x9090).在输出section描述中可以有=FILEEXP属性,它的作用如同FILE()命令,但是FILE命令只作用于该FILE指令之后的section区域,而=FILEEXP 属性作用于整个输出section区域,且FILE命令的优先级更高!!!输出section内命令的关键字:CREATE_OBJECT_SYMBOLS :为每个输入文件建立一个符号,符号名为输入文件的名字.每个符号所在的section是出现该关键字的section. CONSTRUCTORS :与c++内的(全局对象的)构造函数和(全局对像的)析构函数相关,下面将它们简称为全局构造和全局析构.对于a.out目标文件格式,连接器用一些不寻常的方法实现c++的全局构造和全局析构.当连接器生成的目标文件格式不支持任意section名字时,比如说ECOFF、XCOFF格式,连接器将通过名字来识别全局构造和全局析构,对于这些文件格式,连接器把与全局构造和全局析构的相关信息放入出现 CONSTRUCTORS 关键字的输出section内.符号__CTORS_LIST__表示全局构造信息的的开始处,__CTORS_END__表示全局构造信息的结束处.符号__DTORS_LIST__表示全局构造信息的的开始处,__DTORS_END__表示全局构造信息的结束处.这两块信息的开始处是一字长的信息,表示该块信息有多少项数据,然后以值为零的一字长数据结束.一般来说,GNU C++在函数__main内安排全局构造代码的运行,而__main函数被初始化代码(在main函数调用之前执行)调用.是不是对于某些目标文件格式才这样???对于支持任意section名的目标文件格式,比如COFF、ELF格式,GNU C++将全局构造和全局析构信息分别放入.ctors section和.dtors section内,然后在连接脚本内加入如下,__CTOR_LIST__ = .;LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 2)*(.ctors)LONG(0)__CTOR_END__ = .;__DTOR_LIST__ = .;LONG((__DTOR_END__ - __DTOR_LIST__) / 4 - 2)*(.dtors)LONG(0)__DTOR_END__ = .;如果使用GNU C++提供的初始化优先级支持(它能控制每个全局构造函数调用的先后顺序),那么请在连接脚本内把CONSTRUCTORS替换成SORT (CONSTRUCTS),把*(.ctors)换成*(SORT(.ctors)),把*(.dtors)换成*(SORT(.dtors)).一般来说,默认的连接脚本已作好的这些工作.输出section的丢弃:例子,.foo { *(.foo) },如果没有任何一个输入文件包含.foo section,那么连接器将不会创建.foo输出section.但是如果在这些输出section描述内包含了非输入section描述命令(如符号赋值语句),那么连接器将总是创建该输出section.有一个特殊的输出section,名为/DISCARD/,被该section引用的任何输入section将不会出现在输出文件内,这就是DISCARD的意思吧.如果/DISCARD/ section被它自己引用呢?想想看.输出section属性:终于讲到这里了,呵呵.我们再回顾以下输出section描述的文法:SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]{OUTPUT-SECTION-COMMANDOUTPUT-SECTION-COMMAND...} [>REGION] [AT>LMA_REGION] [:PHDR :PHDR ...] [=FILLEXP]前面我们浏览了SECTION、ADDRESS、OUTPUT-SECTION-COMMAND相关信息,下面我们将浏览其他属性.TYPE :每个输出section都有一个类型,如果没有指定TYPE类型,那么连接器根据输出section引用的输入section的类型设置该输出section的类型.它可以为以下五种值,NOLOAD :该section在程序运行时,不被载入内存.DSECT,COPY,INFO,OVERLAY :这些类型很少被使用,为了向后兼容才被保留下来.这种类型的section必须被标记为“不可加载的”,以便在程序运行不为它们分配内存.输出section的LMA :默认情况下,LMA等于VMA,但可以通过关键字AT()指定LMA.用关键字AT()指定,括号内包含表达式,表达式的值用于设置LMA.如果不用AT()关键字,那么可用AT>LMA_REGION表达式设置指定该section加载地址的范围. 这个属性主要用于构件ROM境象.例子,SECTIONS{.text 0x1000 : { *(.text) _etext = . ; }.mdata 0x2000 :AT ( ADDR (.text) + SIZEOF (.text) ){ _data = . ; *(.data); _edata = . ; }.bss 0x3000 :{ _bstart = . ; *(.bss) *(COMMON) ; _bend = . ;}}程序如下,extern char _etext, _data, _edata, _bstart, _bend;char *src = &_etext;char *dst = &_data;while (dst < &_edata) {*dst++ = *src++;}for (dst = &_bstart; dst< &_bend; dst++)*dst = 0;此程序将处于ROM内的已初始化数据拷贝到该数据应在的位置(VMA地址),并将为初始化数据置零.读者应该认真的自己分析以上连接脚本和程序的作用.输出section区域:可以将输出section放入预先定义的内存区域内,例子,MEMORY { rom : ORIGIN = 0x1000, LENGTH = 0x1000 }SECTIONS { ROM : { *(.text) } >rom }输出section所在的程序段:可以将输出section放入预先定义的程序段(program segment)内.如果某个输出section设置了它所在的一个或多个程序段,那么接下来定义的输出section的默认程序段与该输出 section的相同.除非再次显示地指定.例子,PHDRS { text PT_LOAD ; }SECTIONS { .text : { *(.text) } :text }可以通过:NONE指定连接器不把该section放入任何程序段内.详情请查看PHDRS 命令输出section的填充模版:这个在前面提到过,任何输出section描述内的未指定的内存区域,连接器用该模版填充该区域.用法:=FILEEXP,前两字节有效,当区域大于两字节时,重复使用这两字节以将其填满.例子,SECTIONS { .text : { *(.text) } =0x9090 }覆盖图(overlay)描述:覆盖图描述使两个或多个不同的section占用同一块程序地址空间.覆盖图管理代码负责将section的拷入和拷出.考虑这种情况,当某存储块的访问速度比其他存储块要快时,那么如果将section拷到该存储块来执行或访问,那么速度将会有所提高,覆盖图描述就很适合这种情形.文法如下,SECTIONS {...OVERLAY [START] : [NOCROSSREFS] [AT ( LDADDR )]{SECNAME1{OUTPUT-SECTION-COMMANDOUTPUT-SECTION-COMMAND...} [:PHDR...] [=FILL]SECNAME2{OUTPUT-SECTION-COMMANDOUTPUT-SECTION-COMMAND...} [:PHDR...] [=FILL]...} [>REGION] [:PHDR...] [=FILL]...}由以上文法可以看出,同一覆盖图内的section具有相同的VMA.SECNAME2的LMA 为SECTNAME1的LMA加上SECNAME1的大小,同理计算SECNAME2,3,4...的LMA.SECNAME1的LMA由LDADDR决定,如果它没有被指定,那么由START决定,如果它也没有被指定,那么由当前定位符号的值决定.NOCROSSREFS关键字指定各section之间不能交叉引用,否则报错.对于OVERLAY描述的每个section,连接器将定义两个符号__load_start_SECNAME和__load_stop_SECNAME,这两个符号的值分别代表SECNAME section的LMA地址的开始和结束.连接器处理完OVERLAY描述语句后,将定位符号的值加上所有覆盖图内section 大小的最大值.看个例子吧,SECTIONS{...OVERLAY 0x1000 : AT (0x4000){.text0 { o1/*.o(.text) }.text1 { o2/*.o(.text) }}...}.text0 section和.text1 section的VMA地址是0x1000,.text0 section加载于地址0x4000,.text1 section紧跟在其后.程序代码,拷贝.text1 section代码,extern char __load_start_text1, __load_stop_text1; memcpy ((char *) 0x1000, &__load_start_text1,&__load_stop_text1 - &__load_start_text1);。

链接脚本(LinkerScript)用法解析(一)关键字SECTIONS与MEMORY

链接脚本(LinkerScript)用法解析(一)关键字SECTIONS与MEMORY

链接脚本(LinkerScript)⽤法解析(⼀)关键字SECTIONS与MEMORY1.MEMORY关键字⽤于描述⼀个MCU ROM和RAM的内存地址分布(Memory Map),MEMORY中所做的内存描述主要⽤于SECTIONS中LMA和VMA的定义。

2.SECTIONS关键字⽤于定义output section(输出段)的相应input section(输⼊段)、LMA和VMA,是整个连接脚本中最为重要的部分。

注:output section 是实际存储在内存中的“段”,⽽input section是其构成成员,如.data为数据段,由所有全局变量构成(默认情况下);.text为代码段,由所有函数构成(默认情况下)...3.下⾯我们⾸先来介绍MEMORY的语法,MEMORY的语法格式如下:MEMORY{ <name> [(<attr>)] : ORIGIN = <origin>, LENGTH = <len> ...}其中<name>是所要定义的内存区域的名字,<origin>是其起始地址,<len>为内存区域的⼤⼩。

另外,<attr>是可选的,并不重要,具体⽤法可参考GNU Linker 的语法说明。

MEMORY的⼀个具体使⽤实例如下:MEMORY{ rom (rx) : ORIGIN = 0, LENGTH = 256K // MEMORY语法中可以使⽤如K、M和G这样的内存单位 ram (!rx) : org = 0x40000000, l = 4M // ORIGIN可以写为org,⽽LENGTH可以写为l}4.在介绍SECTIONS的⽤法之前,我们先对之前提到的LMA和VMA进⾏说明:每个output section都有⼀个LMA和⼀个VMA,LMA是其存储地址,⽽VMA是其运⾏时地址,例如将全局变量g_Data所在数据段.data的LMA设为0x80000020(属于ROM地址),VMA设为0xD0004000(属于RAM地址),那么g_Data的值将存储在ROM中的0x80000020处,⽽程序运⾏时,⽤到g_Data的程序会到RAM中的0xD0004000处寻找它。

ld链接脚本文件解析之三

ld链接脚本文件解析之三

ld链接脚本文件解析之三为符号赋值.===========================你可以在一个连接脚本中为一个符号赋一个值. 这会把一个符号定义为一个全局符号.简单的赋值.------------------你可以使用所有的C赋值符号为一个符号赋值.`SYMBOL = EXPRESSION ;'`SYMBOL += EXPRESSION ;'`SYMBOL -= EXPRESSION ;'`SYMBOL *= EXPRESSION ;'`SYMBOL /= EXPRESSION ;'`SYMBOL >= EXPRESSION ;'`SYMBOL &= EXPRESSION ;'`SYMBOL |= EXPRESSION ;'第一个情况会把SYMBOL定义为值EXPRESSION. 其它情况下, SYMBOL必须是已经定义了的, 而值会作出相应的调整.特殊符号名'.'表示定位计数器. 你只可以在'SECTIONS'命令中使用它.EXPRESSION后面的分号是必须的.表达式下面会定义.你在写表达式赋值的时候,可以把它们作为单独的部分,也可以作为'SECTIONS'命令中的一个语句,或者作为'SECTIONS'命令中输出节描述的一个部分.符号所在的节会被设置成表达式所在的节.下面是一个关于在三处地方使用符号赋值的例子:floating_point = 0;SECTIONS.text :{*(.text)_etext = .;}_bdata = (. + 3) & ~ 3;.data : { *(.data) }}在这个例子中, 符号`floating_point'被定义为零. 符号'-etext'会被定义为前面一个'.text'节尾部的地址. 而符号'_bdata'会被定义为'.text'输出节后面的一个向上对齐到4字节边界的一个地址值.PROVIDE-------在某些情况下, 一个符号被引用到的时候只在连接脚本中定义,而不在任何一个被连接进来的目标文件中定义. 这种做法是比较明智的. 比如, 传统的连接器定义了一个符号'etext'. 但是, ANSI C需要用户能够把'etext'作为一个函数使用而不会产生错误. 'PROVIDE'关键字可以被用来定义一个符号, 比如'etext', 这个定义只在它被引用到的时候有效,而在它被定义的时候无效.语法是`PROVIDE(SYMBOL = EXPRESSION)'.下面是一个关于使用'PROVIDE'定义'etext'的例子:SECTIONS{.text :{*(.text)_etext = .;PROVIDE(etext = .);}在这个例子中, 如果程序定义了一个'_etext'(带有一个前导下划线), 连接器会给出一个重定义错误. 如果, 程序定义了一个'etext'(不带前导下划线), 连接器会默认使用程序中的定义. 如果程序引用了'etext'但不定义它, 连接器会使用连接脚本中的定义.。

链接脚本文件语法规则

链接脚本文件语法规则

LDS文件语法规则/hpunix/articles/2109690.html最近在看Linux内核时,总是遇到一些和连接脚本相关的东东,搞得人一头雾水,终于下定决心把它搞明白,写下一点心得,希望对和我一样的人有所帮助!连接脚本的格式====================连接脚本是文本文件.你写了一系列的命令作为一个连接脚本. 每一个命令是一个带有参数的关键字,或者是一个对符号的赋值. 你可以用分号分隔命令. 空格一般被忽略.文件名或格式名之类的字符串一般可以被直接键入. 如果文件名含有特殊字符,比如一般作为分隔文件名用的逗号, 你可以把文件名放到双引号中. 文件名中间无法使用双引号.你可以像在C语言中一样,在连接脚本中使用注释, 用'/*'和'*/'隔开. 就像在C中,注释在语法上等同于空格.简单的连接脚本示例============================许多脚本是相当的简单的.可能的最简单的脚本只含有一个命令: 'SECTIONS'. 你可以使用'SECTIONS'来描述输出文件的内存布局.'SECTIONS'是一个功能很强大的命令. 这里这们会描述一个很简单的使用. 让我们假设你的程序只有代码节,初始化过的数据节,和未初始化过的数据节. 这些会存在于'.text','.data'和'.bss'节, 另外, 让我们进一步假设在你的输入文件中只有这些节.对于这个例子, 我们说代码应当被载入到地址'0x10000'处, 而数据应当从0x8000000处开始. 下面是一个实现这个功能的脚本:SECTIONS{. = 0x10000;.text : { *(.text) }. = 0x8000000;.data : { *(.data) }.bss : { *(.bss) }}你使用关键字'SECTIONS'写了这个SECTIONS命令, 后面跟有一串放在花括号中的符号赋值和输出节描述的内容.上例中, 在'SECTIONS'命令中的第一行是向一个特殊的符号'.'赋值, 这是一个定位计数器. 如果你没有以其它的方式指定输出节的地址(其他方式在后面会描述), 那么地址值就会被设为定位计数器的现有值. 定位计数器然后被加上输出节的尺寸. 在'SECTIONS'命令的开始处, 定位计数器拥有值'0'.第二行定义一个输出节,'.text'. 冒号是语法需要,现在可以被忽略. 节名后面的花括号中,你列出所有应当被放入到这个输出节中的输入节的名字. '*'是一个通配符,匹配任何文件名. 表达式'*(.text)'意思是所有的输入文件中的'.text'输入节.因为当输出节'.text'定义的时候, 定位计数器的值是'0x10000',连接器会把输出文件中的'.text'节的地址设为'0x10000'.余下的内容定义了输出文件中的'.data'节和'.bss'节. 连接器会把'.data'输出节放到地址'0x8000000'处. 连接器放好'.data'输出节之后, 定位计数器的值是'0x8000000'加上'.data'输出节的长度. 得到的结果是连接器会把'.bss'输出节放到紧接'.data'节后面的位置.连接器会通过在必要时增加定位计数器的值来保证每一个输出节具有它所需的对齐. 在这个例子中, 为'.text'和'.data'节指定的地址会满足对齐约束, 但是连接器可能会需要在'.data'和'.bss'节之间创建一个小的缺口.就这样,这是一个简单但完整的连接脚本.简单的连接脚本命令.=============================在本章中,我们会描述一些简单的脚本命令.设置入口点.-----------------------在运行一个程序时第一个被执行到的指令称为"入口点". 你可以使用'ENTRY'连接脚本命令来设置入口点.参数是一个符号名:ENTRY(SYMBOL)有多种不同的方法来设置入口点.连接器会通过按顺序尝试以下的方法来设置入口点, 如果成功了,就会停止.* `-e'入口命令行选项;* 连接脚本中的`ENTRY(SYMBOL)'命令;* 如果定义了start, 就使用start的值;* 如果存在,就使用'.text'节的首地址;* 地址`0'.处理文件的命令.---------------------------有几个处理文件的连接脚本命令.`INCLUDE FILENAME'在当前点包含连接脚本文件FILENAME. 在当前路径下或用'-L'选项指定的所有路径下搜索这个文件,你可以嵌套使用'INCLUDE'达10层.`INPUT(FILE, FILE, ...)'`INPUT(FILE FILE ...)''INPUT'命令指示连接器在连接时包含的文件, 就像它们是在命令行上指定的一样.比如,如果你在连接的时候总是要包含文件'subr.o',但是你对每次连接时要在命令行上输入感到厌烦, 你就可以在你的连接脚本中输入'INPUT (subr.o).事实上,如果你喜欢,你可以把你所有的输入文件列在连接脚本中, 然后在连接的时候什么也不需要,只要一个'-T'选项就够了.在一个'系统根前缀'被配置的情况下, 一个文件名如果以'/'字符打头, 并且脚本也存放在系统根前缀的某个子目录下, 文件名就会被在系统根前缀下搜索. 否则连接器就会企图打开当前目录下的文件. 如果没有发现, 连接器会通过档案库搜索路径进行搜索.如果你使用了'INPUT (-lFILE)', 'ld'会把文件名转换为'libFILE.a', 就像命令行参数'-l'一样.当你在一个隐式连接脚本中使用'INPUT'命令的时候, 文件就会在连接时连接脚本文件被包含的点上被包含进来. 这会影响到档案搜索.`GROUP(FILE, FILE, ...)'`GROUP(FILE FILE ...)'除了文件必须全是档案文件之外, 'GROUP'命令跟'INPUT'相似, 它们会被反复搜索,直至没有未定义的引用被创建.`OUTPUT(FILENAME)''OUTPUT'命令命名输出文件. 在连接脚本中使用'OUTPUT(FILENAME)'命令跟在命令行中使用'-o FILENAME'命令是完全等效的. 如果两个都使用了, 那命令行选项优先.你可以使用'OUTPUT'命令为输出文件创建一个缺省的文件名,而不是常用的'a.out'.`SEARCH_DIR(PATH)'`SEARCH_DIR'命令给'ld'用于搜索档案文件的路径中再增加新的路径. 使用`SEARCH_DIR(PATH)'跟在命令行上使用'-L PATH'选项是完全等效的. 如果两个都使用了, 那连接器会两个路径都搜索. 用命令行选项指定的路径首先被搜索.`STARTUP(FILENAME)'除了FILENAME会成为第一个被连接的输入文件, 'STARTUP'命令跟'INPUT'命令完全相似, 就像这个文件是在命令行上第一个被指定的文件一样. 如果在一个系统中, 入口点总是存在于第一个文件中,那这个就很有用.处理目标文件格式的命令.-----------------------------------------有两个处理目标文件格式的连接脚本命令.`OUTPUT_formAT(BFDNAME)'`OUTPUT_formAT(DEFAULT, BIG, LITTLE)'`OUTPUT_formAT'命令为输出文件使用的BFD格式命名. 使用`OUTPUT_formAT(BFDNAME)'跟在命令行上使用'-oformat BFDNAME'是完全等效的. 如果两个都使用了, 命令行选项优先.你可在使用`OUTPUT_formAT'时带有三个参数以使用不同的基于'-EB'和'-EL'的命令行选项的格式.如果'-EB'和'-EL'都没有使用, 那输出格式会是第一个参数DEFAULT, 如果使用了'-EB',输出格式会是第二个参数BIG, 如果使用了'-EL', 输出格式会是第三个参数, LITTLE.比如, 缺省的基于MIPS ELF平台连接脚本使用如下命令:OUTPUT_formAT(elf32-bigmips, elf32-bigmips, elf32-littlemips)这表示缺省的输出文件格式是'elf32-bigmips', 但是当用户使用'-EL'命令行选项的时候, 输出文件就会被以`elf32-littlemips'格式创建.`TARGET(BFDNAME)''TARGET'命令在读取输入文件时命名BFD格式. 它会影响到后来的'INPUT'和'GROUP'命令. 这个命令跟在命令行上使用`-b BFDNAME'相似. 如果使用了'TARGET'命令但`OUTPUT_formAT'没有指定, 最后的'TARGET'命令也被用来设置输出文件的格式.其它的连接脚本命令.----------------------------还有一些其它的连接脚本命令.`ASSERT(EXP, MESSAGE)'确保EXP不等于零,如果等于零, 连接器就会返回一个错误码退出,并打印出MESSAGE.`EXTERN(SYMBOL SYMBOL ...)'强制SYMBOL作为一个无定义的符号输入到输出文件中去. 这样做了,可能会引发从标准库中连接一些节外的库. 你可以为每一个EXTERN'列出几个符号, 而且你可以多次使用'EXTERN'. 这个命令跟'-u'命令行选项具有相同的效果.`FORCE_COMMON_ALLOCATION'这个命令跟命令行选项'-d'具有相同的效果: 就算指定了一个可重定位的输出文件('-r'),也让'ld'为普通符号分配空间.`INHIBIT_COMMON_ALLOCATION'这个命令跟命令行选项`--no-define-common'具有相同的效果: 就算是一个不可重位输出文件, 也让'ld'忽略为普通符号分配的空间.`NOCROSSREFS(SECTION SECTION ...)'这个命令在遇到在某些特定的节之间引用的时候会产生一条错误信息.在某些特定的程序中, 特别是在使用覆盖技术的嵌入式系统中, 当一个节被载入内存时,另外一个节就不会在内存中. 任何在两个节之间的直接引用都会是一个错误. 比如, 如果节1中的代码调用了另一个节中的一个函数,这就会产生一个错误.`NOCROSSREFS'命令带有一个输出节名字的列表. 如果'ld'遇到任何在这些节之间的交叉引用, 它就会报告一个错误,并返回一个非零退出码. 注意,`NOCROSSREFS'命令使用输出节名,而不是输入节名.`OUTPUT_ARCH(BFDARCH)'指定一个特定的输出机器架构. 这个参数是BFD库中使用的一个名字. 你可以通过使用带有'-f'选项的'objdump'程序来查看一个目标文件的架构.为符号赋值.===========================你可以在一个连接脚本中为一个符号赋一个值. 这会把一个符号定义为一个全局符号.简单的赋值.------------------你可以使用所有的C赋值符号为一个符号赋值(该符号同时被定义).`SYMBOL = EXPRESSION ;'`SYMBOL += EXPRESSION ;'`SYMBOL -= EXPRESSION ;'`SYMBOL *= EXPRESSION ;'`SYMBOL /= EXPRESSION ;'`SYMBOL <<= EXPRESSION ;'`SYMBOL >>= EXPRESSION ;'`SYMBOL &= EXPRESSION ;'`SYMBOL |= EXPRESSION ;'第一个情况会把SYMBOL定义为值EXPRESSION. 其它情况下, SYMBOL必须是已经定义了的, 而值会作出相应的调整.特殊符号名'.'表示定位计数器. 你只可以在'SECTIONS'命令中使用它.EXPRESSION后面的分号是必须的.表达式下面会定义.你在写表达式赋值的时候,可以把它们作为单独的部分,也可以作为'SECTIONS'命令中的一个语句,或者作为'SECTIONS'命令中输出节描述的一个部分.符号所在的节会被设置成表达式所在的节.下面是一个关于在三处地方使用符号赋值的例子:floating_point = 0;SECTIONS{.text :{*(.text)_etext = .;}_bdata = (. + 3) & ~ 3;.data : { *(.data) }}在这个例子中, 符号`floating_point'被定义为零. 符号'_etext'会被定义为前面一个'.text'节尾部的地址.而符号'_bdata'会被定义为'.text'输出节后面的一个向上对齐到4字节边界的一个地址值.PROVIDE-------在某些情况下, 一个符号被引用到的时候只在连接脚本中定义,而不在任何一个被连接进来的目标文件中定义. 这种做法是比较明智的. 比如, 传统的连接器定义了一个符号'etext'. 但是, ANSI C需要用户能够把'etext'作为一个函数使用而不会产生错误. 'PROVIDE'关键字可以被用来定义一个符号, 比如'etext', 这个定义只在它被引用到的时候有效,而在它被定义的时候无效.语法是 `PROVIDE(SYMBOL = EXPRESSION)'.下面是一个关于使用'PROVIDE'定义'etext'的例子:SECTIONS{.text :{*(.text)_etext = .;PROVIDE(etext = .);}}在这个例子中, 如果程序定义了一个'_etext'(带有一个前导下划线), 连接器会给出一个重定义错误. 如果,程序定义了一个'etext'(不带前导下划线), 连接器会默认使用程序中的定义. 如果程序引用了'etext'但不定义它, 连接器会使用连接脚本中的定义.SECTIONS命令================'SECTIONS'命令告诉连接器如何把输入节映射到输出节, 并如何把输出节放入到内存中.'SECTIONS'命令的格式如下:SECTIONS{SECTIONS-COMMANDSECTIONS-COMMAND...}每一个SECTIONS-COMMAND可能是如下的一种:* 一个'ENTRY'命令.* 一个符号赋值.* 一个输出节描述.* 一个重叠描述.'ENTRY'命令和符号赋值在'SECTIONS'命令中是允许的, 这是为了方便在这些命令中使用定位计数器. 这也可以让连接脚本更容易理解, 因为你可以在更有意义的地方使用这些命令来控制输出文件的布局.输出节描述和重叠描述在下面描述.如果你在连接脚本中不使用'SECTIONS'命令, 连接器会按在输入文件中遇到的节的顺序把每一个输入节放到同名的输出节中. 如果所有的输入节都在第一个文件中存在,那输出文件中的节的顺序会匹配第一个输入文件中的节的顺序. 第一个节会在地址零处.输出节描述--------------------------一个完整的输出节的描述应该是这个样子的:SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]{OUTPUT-SECTION-COMMANDOUTPUT-SECTION-COMMAND...} [>REGION] [AT>LMA_REGION] [:PHDR :PHDR ...] [=FILLEXP]大多数输出节不使用这里的可选节属性.SECTION边上的空格是必须的, 所以节名是明确的. 冒号跟花括号也是必须的. 断行和其他的空格是可选的.每一个OUTPUT-SECTION-COMMAND可能是如下的情况:* 一个符号赋值.* 一个输入节描述.* 直接包含的数据值.* 一个特定的输出节关键字.输出节名.-------------------输出节的名字是SECTION. SECTION必须满足你的输出格式的约束. 在一个只支持限制数量的节的格式中,比如'a.out',这个名字必须是格式支持的节名中的一个(比如, 'a.out'只允许'.text', '.data'或'.bss').如果输出格式支持任意数量的节, 但是只支持数字,而没有名字(就像Oasys中的情况), 名字应当以一个双引号中的数值串的形式提供.一个节名可以由任意数量的字符组成,但是一个含有任意非常用字符(比如逗号)的字句必须用双引号引起来.输出节描述--------------------------ADDRESS是关于输出节中VMS(virtual memory space)的一个表达式. 如果你不提供ADDRESS, 连接器会基于REGION(如果存在)设置它,或者基于定位计数器的当前值.如果你提供了ADDRESS, 那输出节的地址会被精确地设为这个值. 如果你既不提供ADDRESS也不提供REGION, 那输出节的地址会被设为当前的定位计数器向上对齐到输出节需要的对齐边界的值. 输出节的对齐要求是所有输入节中含有的对齐要求中最严格的一个.比如:.text . : { *(.text) }和.text : { *(.text) }有细微的不同. 第一个会把'.text'输出节的地址设为当前定位计数器的值. 第二个会把它设为定位计数器的当前值向上对齐到'.text'输入节中对齐要求最严格的一个边界.ADDRESS可以是任意表达式; 比如,如果你需要把节对齐到0x10字节边界,这样就可以让低四字节的节地址值为零, 你可以这样做:.text ALIGN(0x10) : { *(.text) }这个语句可以正常工作,因为'ALIGN'返回当前的定位计数器,并向上对齐到指定的值.指定一个节的地址会改变定位计数器的值.输入节描述-------------------------最常用的输出节命令是输入节描述.输入节描述是最基本的连接脚本操作. 你使用输出节来告诉连接器在内存中如何布局你的程序. 你使用输入节来告诉连接器如何把输入文件映射到你的内存中.输入节基础---------------------------一个输入节描述由一个文件名后跟有可选的括号中的节名列表组成.文件名和节名可以通配符形式出现, 这个我们以后再介绍.最常用的输入节描述是,包含在输出节中的所有具有特定名字的输入节. 比如, 包含所有输入'.text'节,你可以这样写:*(.text)这里,'*'是一个通配符,匹配所有的文件名. 为把一部分文件排除在匹配的名字通配符之外, EXCLUDE_FILE可以用来匹配所有的除了在EXCLUDE_FILE列表中指定的文件.比如:(*(EXCLUDE_FILE (*crtend.o *otherfile.o) .ctors))会让除了`crtend.o'文件和`otherfile.o'文件之外的所有的文件中的所有的.ctors节被包含进来.有两种方法包含多于一个的节:*(.text .rdata)*(.text) *(.rdata)上面两句的区别在于'.text'和'.rdata'输入节的输出节中出现的顺序不同. 在第一个例子中, 两种节会交替出现,并以连接器的输入顺序排布. 在第二个例子中,所有的'.text'输入节会先出现,然后是所有的'.rdata'节.你可以指定文件名,以从一个特定的文件中包含节. 如果一个或多个你的文件含有特殊的数据在内存中需要特殊的定位,你可以这样做. 比如:data.o(.data)如果你使用一个不带有节列表的文件名, 那输入文件中的所有的节会被包含到输出节中. 通常不会这样做, 但是在某些场合下这个可能非常有用. 比如:data.o当你使用一个不含有任何通配符的文件名时, 连接器首先会查看你是否在连接命令行上指定了文件名或者在'INPUT'命令中. 如果你没有, 连接器会试图把这个文件作为一个输入文件打开, 就像它在命令行上出现一样.注意这跟'INPUT'命令不一样, 因为连接器会在档案搜索路径中搜索文件.输入节通配符---------------------------------在一个输入节描述中, 文件名或者节名,或者两者同时都可以是通配符形式.文件名通配符'*'在很多例子中都可以看到,这是一个简单的文件名通配符形式.通配符形式跟Unix Shell中使用的一样.`*'匹配任意数量的字符.`?'匹配单个字符.`[CHARS]'匹配CHARS中的任意单个字符; 字符'-'可以被用来指定字符的方讧, 比如[a-z]匹配任意小字字符.`\'转义其后的字符.当一个文件名跟一个通配符匹配时, 通配符字符不会匹配一个'/'字符(在UNIX 系统中用来分隔目录名), 一个含有单个'*'字符的形式是个例外; 它总是匹配任意文件名, 不管它是否含有'/'. 在一个节名中, 通配符字符会匹配'/'字符.文件名通配符只匹配那些在命令行或在'INPUT'命令上显式指定的文件. 连接器不会通过搜索目录来展开通配符.如果一个文件名匹配多于一个通配符, 或者如果一个文件名显式出现同时又匹配了一个通配符, 连接器会使用第一次匹配到的连接脚本. 比如, 下面的输入节描述序列很可能就是错误的,因为'data.o'规则没有被使用:.data : { *(.data) }.data1 : { data.o(.data) }通常, 连接器会把匹配通配符的文件和节按在连接中被看到的顺序放置. 你可以通过'SORT'关键字改变它, 它出现在括号中的通配符之前(比如,'SORT(.text*)'). 当'SORT'关键字被使用时, 连接器会在把文件和节放到输出文件中之前按名字顺序重新排列它们.如果你对于输入节被放置到哪里去了感到很困惑, 那可以使用'-M'连接选项来产生一个位图文件. 位图文件会精确显示输入节是如何被映射到输出节中的.这个例子显示了通配符是如何被用来区分文件的. 这个连接脚本指示连接器把所有的'.text'节放到'.text'中, 把所有的'.bss'节放到'.bss'. 连接器会把所有的来自文件名以一个大写字母开始的文件中的'.data'节放进'.DATA'节中; 对于所有其他文件, 连接器会把'.data'节放进'.data'节中.SECTIONS {.text : { *(.text) }.DATA : { [A-Z]*(.data) }.data : { *(.data) }.bss : { *(.bss) }}输入节中的普通符号.-----------------------------------对于普通符号,需要一个特殊的标识, 因为在很多目标格式中, 普通符号没有一个特定的输入节. 连接器会把普通符号处理成好像它们在一个叫做'COMMON'的节中.你可能像使用带有其他输入节的文件名一样使用带有'COMMON'节的文件名。

ld链接脚本文件解析之五

ld链接脚本文件解析之五

ld链接脚本文件解析之五展开全文输入节中的普通符号.-----------------------------------对于普通符号,需要一个特殊的标识, 因为在很多目标格式中, 普通符号没有一个特定的输入节. 连接器会把普通符号处理成好像它们在一个叫做'COMMON'的节中.你可能像使用带有其他输入节的文件名一样使用带有'COMMON'节的文件名。

你可以通过这个把来自一个特定输入文件的普通符号放入一个节中,同时把来自其它输入文件的普通符号放入另一个节中。

在大多数情况下,输入文件中的普通符号会被放到输出文件的'.bss'节中。

比如:.bss { *(.bss) *(COMMON) }有些目标文件格式具有多于一个的普通符号。

比如,MIPS ELF目标文件格式区分标准普通符号和小普通符号。

在这种情况下,连接器会为其他类型的普通符号使用一个不同的特殊节名。

在MIPS ELF的情况中,连接器为标准普通符号使用'COMMON',并且为小普通符号使用'.common'。

这就允许你把不同类型的普通符号映射到内存的不同位置。

在一些老的连接脚本上,你有时会看到'[COMMON]'。

这个符号现在已经过时了,它等效于'*(COMMON)'。

输入节和垃圾收集---------------------------------------当连接时垃圾收集正在使用中时('--gc-sections'),这在标识那些不应该被排除在外的节时非常有用。

这是通过在输入节的通配符入口外面加上'KEEP()'实现的,比如'KEEP(*(.init))'或者'KEEP(SORT(*)(.sorts))'。

输入节示例---------------------接下来的例子是一个完整的连接脚本。

链接脚本文件(.ld.lds)详解

链接脚本文件(.ld.lds)详解

链接脚本⽂件(.ld.lds)详解链接脚本实例:(STM32F407VG,RT-Thread Studio⽣成的⼯程所含)* linker script for STM32F407ZG with GNU ld*//* Program Entry, set to mark it as "used"and avoid gc */MEMORY{ROM (rx) : ORIGIN = 0x08000000, LENGTH = 1024k /* 1024K flash */RAM (rw) : ORIGIN = 0x20000000, LENGTH = 128k /* 128K sram */}ENTRY(Reset_Handler)_system_stack_size = 0x400;SECTIONS{.text :{. = ALIGN(4);_stext = .;KEEP(*(.isr_vector)) /* Startup code */ . = ALIGN(4); *(.text) /* remaining code */ *(.text.*) /* remaining code */ *(.rodata) /* read-only data (constants) *//* section information for utest */. = ALIGN(4);__rt_utest_tc_tab_start = .;KEEP(*(UtestTcTab))__rt_utest_tc_tab_end = .;. = ALIGN(4);PROVIDE(__ctors_start__ = .);KEEP (*(SORT(.init_array.*)))KEEP (*(.init_array))PROVIDE(__ctors_end__ = .);. = ALIGN(4);_etext = .;} > ROM = 0/* .data section which is used for initialized data */.stack :{. = ALIGN(4);_sstack = .;. = . + _system_stack_size;. = ALIGN(4);_estack = .;} >RAM__bss_start = .;.bss :{. = ALIGN(4);/* This is used by the startup in order to initialize the .bss secion */_sbss = .;*(.bss)*(.bss.*)*(COMMON). = ALIGN(4);/* This is used by the startup in order to initialize the .bss secion */_ebss = . ;*(.bss.init)} > RAM__bss_end = .;_end = .;/* Stabs debugging sections. */.stab 0 : { *(.stab) }.stabstr 0 : { *(.stabstr) }.stab.excl 0 : { *(.stab.excl) }.stab.exclstr 0 : { *(.stab.exclstr) }}特别注意:1 .text section :{} .stack :{} 表⽰输出⽂件包含的 section2 {}⾥⾯的 section,是输⼊⽂件的 section,⽐如 *(.isr_vector) *(.text) *(.rodata) 这些 .isr_vector section .text section .rodata section,都有指定输⼊⽂件,*表⽰所有的输⼊⽂件;所以 *(.isr_vector) 表⽰从所有的输⼊⽂件中获取所有 .isr_vector section 放在⼀块连续的地址空间;main.o(.data) 表⽰从 main.o⽂件中获取所有的 .data section 放在⼀块连续的地址空间3 链接脚本从上往下,如果输⼊⽂件 A 已经被取出 .text section,此后输⼊⽂件 A 就没有 .text section,不能再被获取4 关于 section 的命名,名字前可以包含 .,也可以不包含,⼤多取名会包含 .。

链接脚本文件语法详细讲解

链接脚本文件语法详细讲解

我们对每个c或者汇编文件进行单独编译,但是不去连接,生成很多.o 的文件,这些.o文件首先是分散的,我们首先要考虑的如何组合起来;其次,这些.o文件存在相互调用的关系;再者,我们最后生成的bin文件是要在硬件中运行的,每一部分放在什么地址都要有仔细的说明。

我觉得在写makefile的时候,最为重要的就是ld的理解,下面说说我的经验:首先,要确定我们的程序用没有用到标准的c库,或者一些系统的库文件,这些一般是在操作系统之上开发要注意的问题,这里并不多说,熟悉在Linux编程的人,基本上都会用ld命令;这里,我们从头开始,直接进行汇编语言的连接。

我们写一个汇编程序,控制GPIO,从而控制外接的LED,代码如下;.text.global _start_start:LDR R0,=0x56000010 GPBCON寄存器MOV R1,# 0x00000400str R1,[R0]LDR R0,=0x56000014MOV R1,#0x00000000STR R1,[R0]MAIN_LOOP:B MAIN_LOOP代码很简单,就是一个对io口进行设置然后写数据。

我们看它是如何编译的,注意我们这里使用的不是arm-linux-gcc而是arm-elf-gcc,二者之间没有什么比较大的区别,arm-linux-gcc 可能包含更多的库文件,在命令行的编译上面是没有区别。

我们来看是如何编译的:arm-elf-gcc -g -c -o led_On.o led_On.s 首先纯编译不连接arm-elf-ld -Ttext 0x00000000 -g led_On.o -o led_on_elf用Ttext指明我们程序存储的地方,这里生成的是elf文件,还不是我们真正的bin,但是可以借助一些工具可以进行调试。

然后:arm-elf-objcopy -O binary -S led_on_elf led_on.bin生成bin文件。

makefile语法格式详解

makefile语法格式详解

什么是makefile?或许很多Winodws的程序员都不知道这个东西,因为那些Windows的IDE都为你做了这个工作,但我感觉要作一个好的和 professional的程序员,makefile仍是要懂。

这就仿佛此刻有这么多的HTML的编辑器,但若是你想成为一个专业人士,你仍是要了解 HTML的标识的含义。

特别在Unix下的软件编译,你就不能不自己写makefile了,会不会写makefile,从一个侧面说明了一个人是不是具有完成大型工程的能力。

因为,makefile关系到了整个工程的编译规则。

一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell 脚本一样,其中也可以执行操作系统的命令。

makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。

make是一个命令工具,是一个解释makefile中指令的命令工具,一般来讲,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux 下GNU的make。

可见,makefile都成了一种在工程方面的编译方式。

现在讲述如何写makefile的文章比较少,这是我想写这篇文章的原因。

当然,不同产商的make各不相同,也有不同的语法,但其本质都是在“文件依赖性”上做文章,这里,我仅对GNU的make进行讲述,我的环境是RedHat Linux ,make的版本是。

必竟,这个make是应用最为普遍的,也是用得最多的。

而且其还是最遵循于IEEE 标准的()。

在这篇文档中,将以C/C++的源码作为我们基础,所以必然涉及一些关于C/C++的编译的知识,相关于这方面的内容,还请各位查看相关的编译器的文档。

link.ld基本语法

link.ld基本语法

link.ld基本语法link.ld是一个linker脚本文件,它被用来描述ELF文件的目标模块如何映射到内存中。

通过编写linker脚本文件来控制代码、数据和符号在内存中如何组织的位置和大小。

链结文件由一系列命令组成,每个命令都有一个唯一的操作,在某些情况下,它有一个或多个名称参数。

有两种类型的命令:指示符和选项。

指示符定义输出文件的组织方式,包括代码、数据和符号表等各个部分的位置和大小。

选项定义链接器的行为,如是否定义某个符号为弱符号,是否启用某个特定的选项等等。

1、SECTIONS宏SECTIONS用于定义section列表。

每个section由段名、下一个section在内存中的位置、大小、属性以及与其关联的任何程序头部分配。

例如:.text : {*(.text) /* Keep all code together */}.bss : {上面的例子给出了一个简单的链接器脚本,该脚本定义了三个section:.text、.data和.bss。

这些部分按照它们在源代码中出现的顺序排序,并且每个部分中的所有内容都是连续的。

如果没有给出其他指令,链接器将按照自己的规则放置剩余的部分。

2、MEMORYMEMORY宏定义了存储器的组织方式。

它定义了链接器可以放置部分的物理空间,包括地址和大小。

rom : ORIGIN = 0x00000000, LENGTH = 16K上面的例子定义了两个内存区域rom和ram,它们的物理地址分别从0x00000000和0x10000000开始,并且分别具有16K和4K的容量。

这个定义告诉链接器,ROM可以用来放置. text和rodata,并且可以放置BSS部分的余下部分。

RAM可以用来放置.data和.bss 段。

3、INCLUDEINCLUDE命令可以将外部文件引入到链接器脚本中。

该命令允许将公共的链接器文件分离出来,以便可以多个应用程序使用相同的链接器文件。

ld链接脚本文件解析之六

ld链接脚本文件解析之六

ld链接脚本文件解析之六输出节关键字-----------------------有两个关键字作为输出节命令的形式出现。

`CREATE_OBJECT_SYMBOLS'这个命令告诉连接器为每一个输入文件创建一个符号。

而符号的名字正好就是相关输入文件的名字。

而每一个符号的节就是`CREATE_OBJECT_SYMBOLS'命令出现的那个节。

这个命令一直是a.out目标文件格式特有的。

它一般不为其它的目标文件格式所使用。

`CONSTRUCTORS'当使用a.out目标文件格式进行连接的时候,连接器使用一组不常用的结构以支持C++的全局构造函数和析构函数。

当连接不支持专有节的目标文件格式时,比如ECOFF和XCOFF,连接器会自动辩识C++全局构造函数和析构函数的名字。

对于这些目标文件格式,‘CONSTRUCTORS’命令告诉连接器把构造函数信息放到‘CONSTRUCTORS’命令出现的那个输出节中。

对于其它目标文件格式,‘CONSTRUCTORS’命令被忽略。

符号`__CTOR_LIST__'标识全局构造函数的开始,而符号`__DTOR_LIST'标识结束。

这个列表的第一个WORD是入口的数量,紧跟在后面的是每一个构造函数和析构函数的地址,再然后是一个零WORD。

编译器必须安排如何实际运行代码。

对于这些目标文件格式,GNU C++通常从一个`__main'子程序中调用构造函数,而对`__main'的调用自动被插入到`main'的启动代码中。

GNU C++通常使用'atexit'运行析构函数,或者直接从函数'exit'中运行。

对于像‘COFF’或‘ELF’这样支持专有节名的目标文件格式,GNU C++通常会把全局构造函数与析构函数的地址值放到'.ctors'和'.dtors'节中。

通过简单示例学习链接脚本基本语法

通过简单示例学习链接脚本基本语法
2.4.2.4 TYPE TYPE :每个输出section 都有一个类型,如果没有指定TYPE 类型,那么连接 器根据输出section 引用的输入section 的类型设置该输出section 的类型。 它可以为以下五种值, NOLOAD :该section 在程序运行时,不被载入内存。 DSECT,COPY,INFO,OVERLAY :这些类型很少被使用,为了向后兼容才被保 留下来。这种类型的section 必须被标记为“不可加载的”,以便在程序运行不为 它们分配内存。
.text : { *(.text) }>.sram
SECTION [ADDRESS] [(TYPE)] : [AT(LMA)] { OUTPUT-SECTION-COMMAND OUTPUT-SECTION-COMMAND … } [>REGION] [AT>LMA_REGION] [:PHDR HDR ...] [=FILLEXP]
通过简单示例讲解连接脚本基本语法 王兴伟
Linux博客地址: SOC FPGA客户在采用bm模式或简单实时操作系统时,有时会需要修改工程代 码链接的起始地址和地址空间长度。或者在链接脚本中预留栈空间和定义全局变 量等。本文以骏龙公司提供的baremetal 模式代码工程的脚本为例,介绍一下链 接脚本的基本知识,希望起到抛砖引玉的作用。
.text . : { *(.text) } 和 .text : { *(.text) } 这两个描述是截然不同的,第一个将.text section 的VMA 设置为定位符号的 值,而第二个则是设置成定位符号的修调值,满足对齐要求后的。 ADDRESS 可以是一个任意表达式,比如ALIGN(0×10)这将把该section 的 VMA 设置成定位符号的修调值,满足16 字节对齐后的。 注意:设置 ADDRESS 值,将更改定位符号的值。 另外,可以直接修改的值,如 . = 0×10000;

markdown链接语法

markdown链接语法

markdown链接语法Markdown链接语法有两种:1. 行内式链接行内式链接的语法如下:```[链接文本](链接地址)```其中,方括号中的内容是链接文本,圆括号中的内容是链接地址。

例如,要创建一个链接文本为“百度”的链接,链接地址为“http://www.baiduXXX”,可以使用以下语法:```[百度](http://www.baiduXXX)```渲染结果如下:[百度](http://www.baiduXXX)2. 参考式链接参考式链接分为两部分,第一部分在文章中定义链接,在链接定义处给出链接地址和链接文本;第二部分在其他地方引用该链接。

定义链接的语法如下:```[链接文本][链接标识符][链接标识符]: 链接地址```其中,方括号中的内容是链接文本,方括号后面的内容是链接标识符,冒号后面的内容是链接地址。

例如,要定义一个链接标识符为“baidu”的链接,链接文本为“百度”,链接地址为“http://www.baiduXXX”,可以使用以下语法: ```[百度][baidu][baidu]: http://www.baiduXXX```引用链接的语法如下:```[链接文本][链接标识符]```其中,方括号中的内容是链接文本,方括号后面的内容是链接标识符。

例如,要引用定义好的链接标识符为“baidu”的链接,可以使用以下语法:```[百度][baidu]```渲染结果如下:[百度][baidu]在文章的末尾或者其他地方定义好链接后,就可以在需要引用该链接的地方使用参考式链接了。

这样做的好处是,在文章中多次引用同一个链接时,不必重复输入链接地址和链接文本。

markdown 超链接 语法

markdown 超链接 语法

markdown 超链接语法
Markdown 中创建超链接的基本语法如下:
[链接文本](链接地址"可选的title 属性")
详细解释:
1、[链接文本]:这部分是你希望在文档中显示为链接的文字,用户点击后会跳转到目标网页。

2、(链接地址):这部分是实际要链接的网址或URL。

3、"可选的title 属性":这部分是可选的,如果添加了这个属性,在鼠标悬停在链接上时,浏览器会显示这个title的内容作为提示信息。

注意:
1、Markdown 语法中的括号和方括号必须成对出现。

2、如果链接地址包含特殊字符(如空格),通常需要使用URL 编码规则进行编码。

例如,空格可以用`%20` 替代。

3、标题属性不是所有Markdown 解析器都支持的特性,但大多数现代解析器都会支持它。

jinjia模板语法

jinjia模板语法

jinjia模板语法Jinja是一个流行的Python模板引擎,它允许我们在Python 应用程序中动态生成HTML、XML或其他文本格式的内容。

Jinja模板语法提供了一种简单而灵活的方式来处理动态内容和逻辑控制。

下面我将从多个角度介绍Jinja模板语法的一些重要特性和用法。

1. 变量插值:Jinja使用双花括号{{}}来表示变量插值。

我们可以在模板中插入变量的值,例如:<p>Hello, {{ name }}!</p>。

这里的`name`是一个变量,当渲染模板时,Jinja会将其替换为相应的值。

2. 过滤器:Jinja提供了一系列过滤器,用于对变量进行处理和转换。

例如,我们可以使用`upper`过滤器将变量转换为大写:<p>Hello, {{ name|upper }}!</p>。

这会将`name`变量的值转换为大写后插入到模板中。

3. 控制结构:Jinja支持常见的控制结构,如条件语句和循环语句。

我们可以使用`if`语句根据条件来显示不同的内容:{% if condition %}。

<p>Condition is true.</p>。

{% else %}。

<p>Condition is false.</p>。

{% endif %}。

在这个例子中,根据`condition`的值,模板会显示不同的内容。

4. 循环:Jinja提供了`for`语句来进行循环迭代。

我们可以遍历列表、字典等可迭代对象:<ul>。

{% for item in items %}。

<li>{{ item }}</li>。

{% endfor %}。

</ul>。

在这个例子中,`items`是一个列表,模板会根据列表中的元素生成相应的`<li>`标签。

5. 宏(Macro):宏是Jinja中的一种可重用的代码块,类似于函数。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

我们对每个c或者汇编文件进行单独编译,但是不去连接,生成很多.o 的文件,这些.o文件首先是分散的,我们首先要考虑的如何组合起来;其次,这些.o文件存在相互调用的关系;再者,我们最后生成的bin文件是要在硬件中运行的,每一部分放在什么地址都要有仔细的说明。

我觉得在写makefile的时候,最为重要的就是ld的理解,下面说说我的经验:首先,要确定我们的程序用没有用到标准的c库,或者一些系统的库文件,这些一般是在操作系统之上开发要注意的问题,这里并不多说,熟悉在Linux编程的人,基本上都会用ld命令;这里,我们从头开始,直接进行汇编语言的连接。

我们写一个汇编程序,控制GPIO,从而控制外接的LED,代码如下;.text.global _start_start:LDR R0,=0x56000010 @GPBCON寄存器MOV R1,# 0x00000400str R1,[R0]LDR R0,=0x56000014MOV R1,#0x00000000STR R1,[R0]MAIN_LOOP:B MAIN_LOOP代码很简单,就是一个对io口进行设置然后写数据。

我们看它是如何编译的,注意我们这里使用的不是arm-linux-gcc而是arm-elf-gcc,二者之间没有什么比较大的区别,arm-linux-gcc 可能包含更多的库文件,在命令行的编译上面是没有区别。

我们来看是如何编译的:arm-elf-gcc -g -c -o led_On.o led_On.s 首先纯编译不连接arm-elf-ld -Ttext 0x00000000 -g led_On.o -o led_on_elf用Ttext指明我们程序存储的地方,这里生成的是elf文件,还不是我们真正的bin,但是可以借助一些工具可以进行调试。

然后:arm-elf-objcopy -O binary -S led_on_elf led_on.bin生成bin文件。

-T选项是ld命令中比较重要的一个选项,可以用它直接指明代码的代码段、数据段、bss段,对于复杂的连接,可以专门写一个脚本来告诉编译器如何连接。

-Ttext addr-Tdata addr-Tbss addrarm-elf-ld -Ttext 0x00000000 -g led_On.o -o led_on_elf ,运行地址为0x00000000,由于没有指明数据段和bss,他们会默认的依次放在后面。

相同的代码不同的Ttext,你可以对比一下他们之间会变的差异,ld会自动调整跳转的地址。

第二个概念:section,section可以理解成一块,例如像c里面的一个子函数,就是一个section,链接器ld把object文件中的每个section都作为一个整体,为其分配运行的地址(memory layout),这个过程就是重定位(relocation);最后把所有目标文件合并为一个目标文件。

链接通过一个linker script来控制,这个脚本描述了输入文件的sections到输出文件的映射,以及输出文件的memory layout。

因此,linker总会使用一个linker script,如果不特别指定,则使用默认的script;可以使用‘-T’命令行选项来指定一个linker script。

*映像文件的输入段与输出段linker把多个输入文件合并为一个输出文件。

输出文件和输入文件都是目标文件(object file),输出文件通常被称为可执行文件(executable)。

每个目标文件都有一系列section,输入文件的section称为input section,输出文件的section 则称为output section。

一个section可以是loadable的,即输出文件运行时需要将这样的section加载到memory(类似于RO&RW段);也可以是 allocatable的,这样的section没有任何内容,某些时候用0对相应的memory区域进行初始化(类似于ZI段);如果一个 section既非loadable也非allocatable,则它通常包含的是调试信息。

每个loadable或 allocatable的output section都有两个地址,一是VMA(virtual memory address),是该section的运行时域地址;二是LMA(load memory address),是该section的加载时域地址。

可以通过objdump工具附加'-h'选项来查看目标文件中的sections。

*简单的Linker script(1) SECTIONS命令:The SECTIONS command tells the linker how to map input sections into output sections, and how to place the output sections in memory.命令格式如下:SECTIONS{sections-commandsections-command......}其中sections-command可以是ENTRY命令,符号赋值,输出段描述,也可以是overlay描述。

(2)地址计数器‘.’(location counter):该符号只能用于SECTIONS命令内部,初始值为‘0’,可以对该符号进行赋值,也可以使用该符号进行计算或赋值给其他符号。

它会自动根据SECTIONS命令内部所描述的输出段的大小来计算当前的地址。

(3) 输出段描述(output section description):前面提到在SECTIONS命令中可以作输出段描述,描述的格式如下:section [address] [(type)] : [AT(lma)]{output-section-commandoutput-section-command...} [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]很多附加选项是用不到的。

其中的output-section-command又可以是符号赋值,输入段描述,要直接包含的数据值,或者某一特定的输出段关键字。

*linker script 实例==============================OUTPUT_ARCH(arm)ENTRY(_start)SECTIONS {. = 0xa3f00000;__boot_start = .;.start ALIGN(4) : {*(.text.start)}.setup ALIGN(4) : {setup_block = .;*(.setup)setup_block_end = .;}.text ALIGN(4) : {*(.text)}.rodata ALIGN(4) : {*(.rodata)}.data ALIGN(4) : {*(.data)}.got ALIGN(4) : {*(.got)}__boot_end = .;.bss ALIGN(16) : {bss_start = .;*(.bss)*(COMMON)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 startarm-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。

相关文档
最新文档