Linux lds 文件格式分析(一)

        最近想在自己的内核镜像中加入一段binary,就想着能不能从链接脚本这里入手,分享一下自己最近的心得。

首先是链接脚本的格式:

        连接脚本是文本文件.你写了一系列的命令作为一个连接脚本. 每一个命令是一个带有参数的关键字,或者是一个对符号的赋值. 你可以用分号分隔命令. 空格一般被忽略.文件名或格式名之类的字符串一般可以被直接键入. 如果文件名含有特殊字符,比如一般作为分隔文件名用的逗号, 你可以把文件名放到双引号中. 文件名中间无法使用双引号.你可以象在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'命令指示连接器在连接时包含文件, 就像它们是在命令行上指定的一样.

For example,

        如果你在连接的时候总是要包含文件'my_test.o',但是你对每次连接时要在命令行上输入感到厌烦, 你就可以在你的连接脚本中输入'INPUT (my_test.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字节边界的一个地址值.

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值