您的位置 首页 制造

arm-linux-ld指令 ld链接脚本

我们对每个c或者汇编文件进行单独编译,但是不去连接,生成很多o的文件,这些o文件首先是分散的,我们首先要考虑的如何组合起来;其次

咱们对每个c或许汇编文件进行独自编译,可是不去衔接,生成许多.o 的文件,这些.o文件首要是涣散的,咱们首要要考虑的怎样组合起来;其次,这些.o文件存在彼此调用的联系;再者,咱们最终生成的bin文件是要在硬件中运转的,每一部分放在什么地址都要有细心的阐明。我觉得在写makefile的时分,最为重要的便是ld的了解,下面说说我的经历:

首要,要确认咱们的程序用没有用到规范的c库,或许一些体系的库文件,这些一般是在操作体系之上开发要留意的问题,这儿并不多说,了解在Linux编程的人,基本上都会用ld指令;这儿,咱们从头开端,直接进行汇编语言的衔接。

咱们写一个汇编程序,操控GPIO,然后操控外接的LED,代码如下;

.text

.global _start

_start:

LDR R0,=0x56000010 @GPBCON寄存器

MOV R1,# 0x00000400
str R1,[R0]

LDR R0,=0x56000014
MOV R1,#0x00000000

STR R1,[R0]

MAIN_LOOP:
B MAIN_LOOP

代码很简略,便是一个对io口进行设置然后写数据。咱们看它是怎样编译的,留意咱们这儿运用的不是armlinux-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 addr

arm-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-command

sections-command

……

}

其间sections-command能够是ENTRY指令,符号赋值,输出段描绘,也能够是overlay描绘。

(2) 地址计数器‘.’(location counter):

该符号只能用于SECTIONS指令内部,初始值为‘0’,能够对该符号进行赋值,也能够运用该符号进行核算或赋值给其他符号。它会主动依据SECTIONS指令内部所描绘的输出段的巨细来核算其时的地址。

(3) 输出段描绘(output section description):

前面说到在SECTIONS指令中能够作输出段描绘,描绘的格局如下:

section [address] [(type)] : [AT(lma)]

{

output-section-command

output-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 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):界说本段存储(加载)的地址。

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

ldr r1, _TEXT_BASE

cmp r0, r1

下面,结合u-boot.lds看看一个正式的衔接脚本文件。这个文件的基本功能还能看理解,尽管上面剖析了很多,但其间那些GNU风格的符号仍是着实让我感到利诱。

OUTPUT_FORMAT(“elf32­littlearm”, “elf32­littlearm”, “elf32­littlearm”)
;指定输出可履行文件是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段的完毕方位
}

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/bandaoti/zhizao/263091.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部