链接脚本

    什么是链接脚本,就是用于告诉链接器如何把输入文件内的各个段(section)放到输出文件中,并控制输出文件中的各个段在此程序运行时的地址空间布局。一个程序由多个段组成,那么这些段是如何在文件中存放的,以及是如何加载到内存的相应位置进行执行的呢,这个就是通过连接脚本进行控制的。

链接脚本格式:

    链接脚本由一系列命令组成,每一个命令由一个关键字和相应的参数,或者一些赋值语句等组成。命令由分号进行分割。用/* */进行注释。

常见命令:

    ENTRY(SYMBOL);将SYMBOL的值设置成入口地址。一般设置为_start。
   
    OUTPUT(FILENAME);定义输出文件的名字。可以用它来指定默认的输出文件名称。当然我们一般都用手动-o进行指定,如果我们没有进行手动指定的话,输出文件名称就以这个FILENAME为输出文件名。

    STARTUP(filename);指定filename为第一个输入文件。

    OUTPUT_FORMAT(default, big, little);定义3种输出文件的格式。若有命令行选项-EB(大端),则使用第二个输出格式,有命令行指定-EL(小端),则使用第三个格式。否则使用默认的default输出格式。

    OUTPUT_ARCH(arch);设置输出文件的体系架构。

    SECTIONS命令:最重要的,最基本的,也是最主要的命令,它告诉链接器如何把输入文件的各个section输出到目标文件中的各个section中去。
   
        SECTIONS命令的格式如下:

        SECTIONS
        {
                一条或者多条section-command
                或者符号赋值语句
        }
       
        section-command的常见格式如下:

        secname [address] : [AT(LMA)]
        { contents }

            首先中括号的选项是可选的,可以不写。
       
            secname, 指定输出的段名称。
            address, 表示程序的VMA地址。也就是表示当执行此程序的时候程序加载器应该把这个段加载到内存的哪个地址。如果没有指定这个地址,链接器根据定位符号‘.‘的值设置该section的VMA。
            AT, 后面跟LMA, 这个是表示当我们把目标文件拷贝成二进制的时候,该段在文件中物理存放位置的偏移。这个可以用来把多个不同的部分的代码写到一个文件中,然后烧写到flash上去,然后,程序在运行的时候再把它从AT指定的位置读到内存的另外一个位置上去。

            contents,内容里面指定把哪些文件里面的哪些段或者该文件全部输出到secname所指定的这个段中。比如*(.text)就表示所有输入文件的.text段。括号外面表示文件名称,括号里面表示这些文件里面的什么段。

        例子:

            SECTIONS {
                    . = 0x30000000;         //表示设置当前符号的值为0x3000000
                    .text : { *(.text) }    //表示把所有输入文件的代码段集合在一起,起始运行地址就为当前定位符号的值,-- 0x30000000
                    .rodata ALIGN(4) : {*(.rodata) }   // 在输出文件中它紧挨着.text段存放。
            }
           

常见的例子:

(1):

ENTRY(_start);
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm","elf32-littlearm");
OUTPUT_ARCH(arm);

SECTIONS
{
        . = 0x50008000;

        . = ALIGN(4);
        .text : {
                *(.text);
        }

        . = ALIGN(4);
        .rodata : {
                *(.rodata);
        }

        . = ALIGN(4);
        .data : {
                *(.data);
        }

        . = ALIGN(4);
        .bss : {
                *(.bss);
        }
}

    这个链接脚本表示代码段从0x30000000开始加载,然后后面的.rodata,.data,.bss段都分别加在其后,并且后面的每个段的起始地址是按照4个字节对齐的。

(2):
               
ENTRY(_start);
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm","elf32-littlearm");
OUTPUT_ARCH(arm);

SECTIONS
{
    start 0x00000000 : { start.o }
    main 0x30000000 : AT(4096) { main.o hello.o }
}
           
    上面表示把start.o的运行地址指定为0x000000,
然后main.o hello.o程序的运行地址指定为0x30000000,当我们把链接后生成的可执行文件通过objcopy出来之后,那么start.o的二进制代码就从文件的0偏移开始存放,main.o
hello.o就从同一个文件的4096这个位置开始存放。当时main.o hello.o是挨着存放的,并没有把他们的相同段放在同一个段里面。

======================================

ENTRY(_start);
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm","elf32-littlearm");
OUTPUT_ARCH(arm);

SECTIONS
{
        first 0x0 : {
                led.o
        }

        .text 0x30000000 : AT(4096){
                *(.text);
        }

        .text 0x32000000 : AT(5100){
                a*.o(.text)
        }

        .rodata : {
                *(.rodata);
        }

        .data : {
                *(.data);
        }

        _bss_start = .;
        .bss : {
                *(.bss);
        }
        _bss_end = .;
}

    其实secname在copy成binary文件的时候已经没有了,这个只是在有操作系统的情况下才有用。因此在拷贝成二进制的时候,前面的段名称根本不是很重要,只是后面的的地址和在文件中的地址才是最关心的。

    如果我们在SECTIONS内部定义了变量,那么这个相当于在这个位置定义了一个变量,如果我们要取这个变量地址,我们需要在C语言中使用&_bss_start来访问。

时间: 2024-10-28 12:17:24

链接脚本的相关文章

Linux链接脚本学习--lds

l一.概论 ld: GNU的链接器. 用来把一定量的目标文件跟档案文件链接在一起,并重新定位它们的数据,链接符号引用. 一般编译一个程序时,最后一步就是运行ld进行链接 每一个链接都被一个链接脚本所控制,这个脚本是用链接命令语言书写的. 二.链接脚本 链接脚本的一个主要目的是描述输入文件中的各个段(数据段,代码段,堆,栈,bss)如何被映射到输出文件中,并控制输出文件的内存排布. 链接器总是使用链接脚本的,如果你不提供,则链接器会使用一个缺省的脚本,这个脚本是被编译进链接器可执行文件的. 可以使

U-boot链接地址的详解

1.运行地址<--->链接地址:他们两个是等价的,只是两种不同的说法. 2.加载地址<--->存储地址:他们两个是等价的,也是两种不同的说法. 运行地址:程序在SRAM.SDRAM中执行时的地址.就是执行这条指令时,PC应该等于这个地址,换句话说,PC等于这个地址时,这条指令应该保存在这个地址内. 加载地址:程序保存在Nand flash中的地址. 位置无关码:B.BL.MOV都是位置位置无关码. 位置有关码:LDR PC,=LABEL等类似的代码都是位置有关码. 先看一个链接脚本

whole archive-链接脚本中符号查找问题

问题描述 链接脚本中符号查找问题 文件1: test1.c #include int test1() { printf("testrn"); } 文件2:test2.c #include int test2() { test1(); } 文件3:test.c #include int main() { test2(); } 编译: gcc -c test1.c gcc -c test2.c gcc -c test.c ar r test1.a test1.o ar r test2.a

第二章——静态链接

静态链接 1 编译和链接 1.1 被隐藏了的过程 例如: #include<stdio.h> int main() { printf("Hello World\n"); return 0; } 在Linux下,使用GCC编译: gcc hello.c ./a.out Hello World 事实上,上述过程由4个步骤,分别是预处理.编译.汇编和链接,如图所示:   1.1.1 预编译 首先是源代码hello.c和相关的头文件,如stdio.h等被预编译器cpp预编译成一个.

ld 脚本浅析-LD手册粗糙翻译

本文乃转载, 我在其基础上做了少量修改. 原作者的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作为文件的后缀

2016年年终CSDN博客总结

2015年12月1日,结束了4个月的尚观嵌入式培训生涯,经过了几轮重重面试,最终来到了伟易达集团.经过了长达3个月的试用期,正式成为了伟易达集团的助理工程师. 回顾一年来的学习,工作,生活.各种酸甜苦辣,庆幸是有一群支持我的同事小伙伴,他们同样来自尚观IT培训机构,4年前,他们也是一样,怀着自己的理想考上了理想的大学,4年后,怀着自己的理想通过4个月的培训晋升,巩固自己的知识体系,最终也是找到了一份满意的工作,来到了VTECH, 这一年,收获还是非常大的,获得了公司的升职,同时自己的CSDN博客

安卓修炼之路必要要知道的要求和建议

偶然在一个群中看到有人转发了这篇文章,感觉写得真不错,所以转发下来.向大师学习! (一)成为Android高手必须掌握的8项基本要求 [1] Android操作系统概述 1. Android系统架构.            2. Android利用设计理念.            3. Android 开源知识.            4. Android 参考网站与权威信息.[2] Android SDK及其开发环境搭建            1. Android SDK的版本发布.     

嵌入式 uboot以及kernel添加看门狗临时记录(个人记录未整理乱)

Uboot_Kernerl_Add_Watch_Dog:   U-Boot 2010.06 (Nov 01 2013 - 15:28:44) DRAM:  128 MiBCheck spi flash controller v350... FoundSpi(cs1) ID: 0xEF 0x40 0x18 0x00 0x00 0x00Spi(cs1): Block:64KB Chip:16MB Name:"W25Q128B"*** Warning - bad CRC, using def

U-Boot启动过程

开发板上电后,执行U-Boot的第一条指令,然后顺序执行U-Boot启动函数.看一下board/smdk2410/u-boot.lds这个链接脚本,可以知道目标程序的各部分链接顺序.第一个要链接的是cpu/arm920t/start.o,那么U-Boot的入口指令一定位于这个程序中.下面分两阶段介绍启动流程:                    第一阶段 1.cpu/arm920t/start.S 这个汇编程序是U-Boot的入口程序,开头就是复位向量的代码. _start: b