版主
主题
帖子
积分10609
阅读权限200
注册时间2008-11-22
最后登录1970-1-1
在线时间 小时
|
楼主 |
发表于 2016-12-6 16:31
|
显示全部楼层
0 `9 q& [5 l( g- tld链接脚本文件语法解析之一7 x9 r# B( S6 l( y) Y! \
' Y7 y- c T2 l连接脚本
2 R6 D. |, E! p9 F0 i% J9 e! i& B7 h3 H5 v* U
**************, B; i( f/ Z: s; }6 } g9 ~: [
2 E' k5 ?3 w8 P9 | , K% q0 ]6 j4 }6 D& W
. r+ w6 g: Z) V
连接脚本的一个主要目的是描述输入文件中的节如何被映射到输出文件中,并控制输出文件的内存排布. 几乎所有的连接脚本只做这两件事情. 但是,在需要的时候,连接器脚本还可以指示连接器执行很多其他的操作.这通过下面描述的命令实现.
" q: o4 N+ p S: a5 o' B# ~8 S: b$ U( t2 Q
连接器总是使用连接器脚本的.如果你自己不提供, 连接器会使用一个缺省的脚本,这个脚本是被编译进连接器可执行文件的. 你可以使用'--verbose'命令行选项来显示缺省的连接器脚本的内容. 某些命令行选项,比如" _" L" S4 m: C2 B
; Y" k: b9 M: r) Y/ V+ `$ G'-r'或'-N', 会影响缺省的连接脚本.
$ F7 ?9 w& s* i0 f5 K& u6 B+ o, s6 j5 S( J1 A4 @
你可以过使用'-T'命令行选项来提供你自己的连接脚本. 当你这么做的时候, 你的连接脚本会替换缺省的连接脚本.9 C, y5 t( ]/ e/ Y) `+ @' }; J
: C: u* b% }5 o4 m* i8 U
你也可以通过把连接脚本作为一个连接器的输入文件来隐式地使用它,就象它们是一个被连接的文件一样.
, H$ ]1 a. `% _ M+ }
8 Y$ L6 i+ [8 J, | $ f& |+ t) \/ l$ l+ a
. Z: h& |+ G: u, H5 h基本的连接脚本的概念
0 M6 q% h) `; v5 g: Y% Z' r
1 U2 E; h( y7 f; n============================. N) f ~2 c4 B3 g) E( C* D3 h
2 Q/ b( b3 c6 k+ | e1 Z) e
我们需要定义一些基本的概念与词汇以描述连接脚本语言.
, e' K" R) E9 k& Z: O0 g% V) I$ s& M6 J
连接器把多个输入文件合并成单个输出文件. 输出文件和输入文件都以一种叫做'目标文件格式'的数据格式形式存在. 每一个文件被叫做'目标文件'. 输出文件经常被叫做'可执行文件',但是由于需要,我们也把它叫做目标文件. 每一个目标文件中,在其它东西之间,有一个节列表.我们有时把输入文件的节叫做输入节; 相似的,输出文件中的一个节经常被叫做输出节.
% r3 u' \( v' G, P9 f9 y+ e" \- b6 p
8 V2 o0 W9 ^' \7 ^5 |3 p" r一个目标文件中的每一个节都有一个名字和一个大小尺寸. 大多数节还有一个相关的数据块, 称为节内容. 某一个节可能被标式讵'loadable',含义是在输出文件被执行时,这个节应当被载入到内存中去. 一个没有内容的节可能是'allocatable', 含义是内存中必须为这个节开辟一块空间,但是没有实际的内容载入到这里(在某些情况下,这块内存必须被标式讵零). 一个既不是loadable也不是allocatable的节一般含有一些调试信息.
B, v* j" h# S# n |, ^2 O
; w1 x) s- t! j; y& n! o/ ^每一个loadable或allocatable的输出节有两个地址. 第一个是'VMA'或称为虚拟内存地址. 这是当输出文件运行时节所拥有的地址. 第二个是"LMA', 或称为载入内存地址. 这个节即将要载入的内存地址. 这大多数情况下这两个地址是相同的. 它们两个有可能不同的一个例子是当一个数据节在ROM中时, 当程序启动时,被拷贝到RAM中(这个技术经常被用在基于ROM的系统中进行全局变量的初始化). 在这种情况下, ROM地址就是LMA, 而RAM地址就是VMA.
9 R% {( e" q. t. M: \2 R
) p/ Y9 @ V( V5 z. c. S你可以通过使用带有'-h'选项的'objdump'来察看目标文件中的节.
* Q& o/ j% |, |( n0 C$ U1 L6 D y0 e2 G
每一个目标文件还有一个关于符号的列表, 被称为'符号表'. 一个符号可能是定义过了的,也可能是未定义的.
) R a( V, w1 O( B
" W' U" q6 h+ d) c$ g7 O+ F每一个符号有一个名字, 而且每一个定义的符号有一个地址. 如果你把一个C/C++程序编译为一个目标文件,对于每一个定义的函数和全局或静态变量,你为得到一个定义的符号. 每一个在输入文件中只是一个引用而未定义的函数或全局变量会变成一个未定义的符号.( i5 m& E) h+ w
2 E' @* Y* Z3 h. Z/ o; M* l你可以使用'nm'程序来看一个目标文件中的符号, 或者使用'objdump'程序带有'-t'选项.
/ n) I3 E5 i) g W8 N
& M. n9 c) `0 m- g
) _7 q" {% |6 Y! @# c4 \" S- g% `5 g& b7 T: X- X& C2 O. D
连接脚本的格式
0 X3 ]: p8 x2 c( K: H. Z1 R" S- r" U
====================+ Q! d; d, T. c9 [8 X2 [1 H
( { u* v+ \+ [2 r
连接脚本是文本文件.# [ ~$ {9 L4 H# n: h# d( l
. [9 G4 ^2 \) ~+ G/ r
你写了一系列的命令作为一个连接脚本. 每一个命令是一个带有参数的关键字,或者是一个对符号的赋值. 你可以用分号分隔命令. 空格一般被忽略.
b& h S* g& q" }" U! J( L- T
文件名或格式名之类的字符串一般可以被直接键入. 如果文件名含有特殊字符,比如一般作为分隔文件名用的逗号, 你可以把文件名放到双引号中. 文件名中间无法使用双引号.6 U3 @0 W0 z+ b, B$ z9 x
% F: O1 |0 H* |
你可以象在C语言中一样,在连接脚本中使用注释, 用'/*'和'*/'隔开. 就像在C中,注释在语法上等同于空格.
( _" D& _1 y. g- H
0 I4 A8 H6 B N
" Y! m" \. o+ }& m+ a0 M( }1 S* R- b% y! f5 q0 z
! _* ?) h. Z# q+ Y2 R" j- [
1 J: M( D. k% i% O简单的连接脚本示例
1 w$ g: l2 H" M- s; X$ Y
3 ~: s3 p5 i4 u2 H V. b4 B; D============================+ ^' t+ s; P6 h
( |4 x# d) }# R许多脚本是相当的简单的.: Y8 {1 p7 }1 y( v* p6 @8 ~. Z
! }% g" i0 I! q: v! {3 X2 \
可能的最简单的脚本只含有一个命令: 'SECTIONS'. 你可以使用'SECTIONS'来描述输出文件的内存布局.
2 R1 N. ?) I( A0 y( l n
% c8 U- k, W$ m& a'SECTIONS'是一个功能很强大的命令. 这里这们会描述一个很简单的使用. 让我们假设你的程序只有代码节, 初始化过的数据节, 和未初始化过的数据节. 这些会存在于'.text','.data'和'.bss'节, 另外, 让我们进一步假设在你的输入文件中只有这些节.0 `2 h0 I4 A1 o* \6 i1 t
5 M% r w2 [3 V! ]* a) S
对于这个例子, 我们说代码应当被载入到地址'0x10000'处, 而数据应当从0x8000000处开始. 下面是一个实现这个功能的脚本:
# p% ]2 H- m5 E6 o9 I9 b5 r/ X# c3 [% E z. Q$ [
SECTIONS
7 z2 v7 f9 { r( O P! z8 Y" ~4 j% P1 i+ J7 h' r6 f# m; J' F9 v
{* { W2 N! J2 g) a
# l$ N7 W8 G2 n1 E. = 0x10000;% Y. e0 ]" h* S# z/ W% r( J! ~
6 Z" f7 _1 A% d& j! \.text : { *(.text) }
& r) s* a6 {/ c7 ^1 {8 W/ T, L2 a' O7 F$ \; \; V8 d- b
. = 0x8000000;
/ N r( I* w# J( s% v' X* d) o5 @; e l1 a/ V
.data : { *(.data) }* S% J( e$ b3 f5 `9 p$ i" G
0 g0 Y4 a) B8 {/ M- U7 L2 p, Z.bss : { *(.bss) }
" l H7 o% T9 F6 h0 Q
3 G @" \$ k, Z( l}
0 O3 _& {& W: u
6 Q: `. u& i' e' D# d: O) I你使用关键字'SECTIONS'写了这个SECTIONS命令, 后面跟有一串放在花括号中的符号赋值和输出节描述的内容.; g+ l3 n4 y2 ~5 Z* p
8 X& _4 T2 C8 X" v) s, T上例中, 在'SECTIONS'命令中的第一行是对一个特殊的符号'.'赋值, 这是一个定位计数器. 如果你没有以其它的方式指定输出节的地址(其他方式在后面会描述), 那地址值就会被设为定位计数器的现有值. 定位计数器然后被加上输出节的尺寸. 在'SECTIONS'命令的开始处, 定位计数器拥有值'0'.; D0 c+ c# f0 D; r8 q, f
4 U' g( m- M" e, p' U7 c
第二行定义一个输出节,'.text'. 冒号是语法需要,现在可以被忽略. 节名后面的花括号中,你列出所有应当被放入到这个输出节中的输入节的名字. '*'是一个通配符,匹配任何文件名. 表达式'*(.text)'意思是所有的输入文件中的'.text'输入节.5 E' v" z- S# Y2 L7 Y. ]7 T
& X9 W, R6 w0 K# R因为当输出节'.text'定义的时候, 定位计数器的值是'0x10000',连接器会把输出文件中的'.text'节的地址设为'0x10000'.
& [8 {& Z. t& \- Y! f6 V1 f; P" u% E0 X0 j" H/ P$ o
余下的内容定义了输出文件中的'.data'节和'.bss'节. 连接器会把'.data'输出节放到地址'0x8000000'处. 连接器放好'.data'输出节之后, 定位计数器的值是'0x8000000'加上'.data'输出节的长度. 得到的结果是连接器会把'.bss'输出节放到紧接'.data'节后面的位置.8 v# u: Q' |/ b; }" p
" P" F1 S; [5 F4 c# Y连接器会通过在必要时增加定位计数器的值来保证每一个输出节具有它所需的对齐. 在这个例子中, 为'.text'和'.data'节指定的地址会满足对齐约束, 但是连接器可能会需要在'.data'和'.bss'节之间创建一个小的缺口.$ {* l2 f0 ~0 b9 y& y% ^ y
4 y( R1 x5 ~: I就这样,这是一个简单但完整的连接脚本.
' C8 ^9 |% f6 F: Z( O* G1 f3 ^
. z( o' k, H% F6 s6 Q- K2 z6 I8 `9 | ]9 F5 }! @- p" _5 K: ~
6 s* H% u" t. z: m$ M2 l$ t, n每个连接都被一个'连接脚本'所控制. 这个脚本是用连接命令语言书写的. |
|