Linker Scripts3--简单的链接脚本命令2-Assigning Values to Symbols
1.前言
本章继续讲述简单脚本命令的后半部分
2.Assigning Values to Symbols
你可以给一个符号(symbol)赋值,它会把这些定义的符号放入全局符号表(symbols table)中
2.1 Assigning Values to Symbols
可以使用下面的C操作为一个符号指定值:
symbol = expression ;
symbol += expression ;
symbol -= expression ;
symbol *= expression ;
symbol /= expression ;
symbol <<= expression ;
symbol >>= expression ;
symbol &= expression ;
symbol |= expression ;
注:[1]第一条语句为symbol赋值,其它情况symbol必需提前定义。
[2]特别的符号名:"."代表位置计数符,只能在SECTIONS命令中使用
[3]赋值语句后面必需跟一个;
[4]可以把符号赋值作为一个命令写在它的右侧;也可以作为声明写在SECTIONS命令里面;或者作为SECTIONS命令中输出section的一部分
如下示例显示了赋值语句位于不同的位置:
floating_point = 0; SECTIONS { .text : {46 The GNU linker *(.text) _etext = .; } _bdata = (. + 3) & ~ 3; .data : { *(.data) } }
在这个例子中,‘floating_point’符合被定义为0。‘_etext’符号被定义为‘.text’输入段最后的地址。‘_bata’符号被定义为‘.text’输出段最后按4字节对齐的地址
2.2 PROVIDE
很多情况下,脚本中定义的符号任何对象只能对其引用而不能定义它,比如传统链接器定义的'etext'符号。
然而,ANSI C要求用户使用‘etext’符号作为函数名而不能报错,在程序仅仅是引用某个符号而非定义时,RPOVIDE关键字可以定义一个符号,比如‘etext’,语法是PROVIDE(symbol = expression)
下面是一个‘etext’使用的例子:
SECTIONS { .text : { *(.text) _etext = .; PROVIDE(etext = .); } }
在这个例子中,如果程序定义‘_etext’(带有前导下划线),链接器会报很多重定义错误。
另一方面,如果程序定义‘etext’(没有前导下划线),那么链接器会默默地使用程序中的定义,如果程序中只是引用了‘etext’而没有定义它,链接器则使用脚本中的定义
2.3 PROVIDE_HIDDEN
类似PROVIDE的用法。对于ELF目标文件,定义的符号会被隐藏不会被导出
补充:如果你查看链接后的文件的符号表,会发现它的符号绑定信息是LOCAL,即内部可见的
2.4 Source Code Reference
从源代码来访问链接脚本中定义的变量其实并不直观,脚本中的符号和高级语言中定义的变量不能等同,它是一个符号而不是一个值
在进一步讨论之前,需要注意的是当源代码中的符号名字被放入符号表(symbol table)中时,编译器通常会把它们进行转换。
举例来说,Fortran语言编译器通常会加前导或后接一个下划线,C++编译器会进行扩展的‘name mangling’。
因此在源代码中的使用的变量和在链接脚本中定义的变量是可能有差异的。举例来说,C语言中引用脚本中的变量像这样:
extern int foo;
但是在链接脚本中它被定义为这样:
_foo = 1000;
不过,在下面的例子中我们假设没有名称转换
当一个符号被声明,比如用C语言,那么会有两件事情发生,第一件事情是编译器会保留一块内存空间来存储这个符号的值,
第二件事情是编译器会在符号表中创建一个符号并存储好该符号的地址,符号表中包含的是存储该符号的值的地址,比如在文件中这样一个C语言的声明:
int foo = 1000;
会做如下两件事情:
(1)在内存中留出4字节空间,存储1000
(2)在符号表中创建一个‘foo’的符号,符号的地址为整数型数字1000存储在内存中的地址
当一个程序要引用一个符号时,编译器首先会生成一段去符号表中找到该符号内存块地址的代码,然后在生成一段读取内存块值的代码,比如:
foo = 1;
去符号表中查询‘foo’符号的地址值,然后往这个地址里写值1,然而:
int * a = & foo;
去符号表中查询‘foo’符号的地址值,然后将地址直接拷贝到变量‘a’的内存块里面
对比一下链接脚本中的符号声明,它会在符号表中创建一个符号,但是不会给它分配任何内存,所以它们仅仅是一个地址没有值,比如脚本中这样定义:
foo = 1000;
在符号表中创建一个‘foo’的符号,然后存储它的地址值为1000,但地址1000指向的存储位置没有任何意义。
这就意味着你不能访问链接脚本中定义的符号的值(它没有值),你唯一能做的就是访问符号的地址
注:foo并不是一个变量,它只是一个值,并不需要在内存中留出一段空间来保存它;
在C语言中,符号表中会有一个名为foo的项,这个项目中的值(地址值)是1000;
注意,这个1000并没有实际存在的内存
因此,当你在源代码中使用一个链接脚本定义的符号时,你应该使用它的地址,而不去尝试使用它的值。
举例,假设你想拷贝一个叫.ROM段的内容到.FLASH段中,链接脚本这样声明的:
start_of_ROM = .ROM; end_of_ROM = .ROM + sizeof (.ROM) - 1; start_of_FLASH = .FLASH;
你的C语言代码应该这样写:
extern char start_of_ROM, end_of_ROM, start_of_FLASH; memcpy (& start_of_FLASH, & start_of_ROM, & end_of_ROM - & start_of_ROM);
注意,使用‘&’操作也是完全正确的,使用取址符号&去得到它在符号表中的值