Linux下的lds链接脚本简介(三)

Stella981
• 阅读 705

八、 内存区域命令

在默认情形下,连接器可以为section在程序地址空间内分配任意位置的存储区域。 并通过输出 section描述的 > REGION属性 显示地将该输出section限定于在程序地址空间内的某块存储区域,当存储区域大小不能满足要求时,连接器会报告该错误 。

你也可以用MEMORY命令让 在SECTIONS命令内 *未*引用 的 selection 分配在程序地址空间内的某个存储区域内。

注意:以下存储区域指的是在程序地址空间内的。

MEMORY命令的文法如下,

MEMORY {

NAME1 [(ATTR)] : ORIGIN = ORIGIN1, LENGTH = LEN1

NAME2 [(ATTR)] : ORIGIN = ORIGIN2, LENGTH = LEN2

}

NAME :存储区域的名字,这个名字可以与符号名、文件名、section名重复,因为它处于一个独立的名字空间。

ATTR :定义该存储区域的属性,在讲述SECTIONS命令时提到,当某输入section没有在SECTIONS命令内引用时,连接器会把该输入 section直接拷贝成输出section,然后将该输出section放入内存区域内。如果设置了内存区域设置了ATTR属性,那么该区域只接受满足该属性的section(怎么判断该section是否满足?输出section描述内好象没有记录该section的读写执行属性)。

ATTR属性内可以出现以下7个字符,

R 只读section

W 读/写section

X 可执行section

A ‘可分配的’section

I 初始化了的section

L 同I

! 不满足该字符之后的任何一个属性的section

ORIGIN :关键字,区域的开始地址,可简写成org或o

LENGTH :关键字,区域的大小,可简写成len或l

示例

MEMORY

{

rom (rx) : ORIGIN = 0, LENGTH = 256K

ram (!rx) : org = 0×40000000, l = 4M

}

此例中 ,把在SECTIONS命令内*未*引用的且具有读属性或写属性的输入section放入rom区域内,把其他未引用的输入section放入 ram。如果某输出section要被放入某内存区域内,而该输出section又没有指明ADDRESS属性,那么连接器将该输出section放在该区域内下一个能使用位置。

九、 PHDRS命令

该命令仅在产生ELF目标文件时有效。

ELF目标文件格式用program headers程序头(程序头内包含一个或多个segment程序段描述)来描述程序如何被载入内存 。可以用objdump -p命令查看。

当在本地ELF系统运行ELF目标文件格式的程序时,系统加载器通过读取程序头信息以知道如何将程序加载到内存。要了解系统加载器如何解析程序头,请参考ELF ABI文档。

在连接脚本内不指定 PHDRS 命令时,连接器能够很好的创建程序头,但是有时需要更精确的描述程序头,那么 PAHDRS 命令就派上用场了。

注意:一旦在连接脚本内使用了 PHDRS命令,那么连接器**仅会**创建PHDRS命令指定的信息,所以使用时须谨慎。

PHDRS命令文法如下,

PHDRS

{

NAME TYPE [ FILEHDR ] [ PHDRS ] [ AT ( ADDRESS ) ]

[ FLAGS ( FLAGS ) ] ;

}

其中FILEHDR、PHDRS、AT、FLAGS为关键字。

NAME :为程序段名,此名字可以与符号名、section名、文件名重复,因为它在一个独立的名字空间内。此名字只能在SECTIONS命令内使用。

一个程序段可以由多个‘可加载’的section组成。通过输出section描述的属性 :PHDRS 可以将输出section加入一个程序段, : PHDRS 中的 PHDRS 为程序段名。在一个输出section描述内可以多次使用 :PHDRS 命令,也即可以将一个section加入多个程序段。

如果在一个输出section描述内指定了 :PHDRS 属性,那么其后的输出section描述将默认使用该属性,除非它也定义了:PHDRS属性。显然当多个输出section属于同一程序段时可简化书写。

TYPE可以是以下八种形式,

PT_NULL 0

表示未被使用的程序段

PT_LOAD 1

表示该程序段在程序运行时应该被加载

PT_DYNAMIC

表示该程序段包含动态连接信息

PT_INTERP 3

表示该程序段内包含程序加载器的名字,在linux下常见的程序加载器是ld-linux.so.2

PT_NOTE 4

表示该程序段内包含程序的说明信息

PT_SHLIB 5

一个保留的程序头类型,没有在ELF ABI文档内定义

PT_PHDR 6

表示该程序段包含程序头信息。

EXPRESSION 表达式值

以上每个类型都对应一个数字,该表达式定义一个用户自定的程序头。

在TYPE属性后存在FILEHDR关键字,表示该段包含ELF文件头信息;存在PHDRS关键字,表示该段包含ELF程序头信息。

AT(ADDRESS) 属性定义该程序段的加载位置(LMA),该属性将**覆盖**该程序段内的section的AT()属性。

默认情况下,连接器会根据该程序段包含的section的属性(什么属性?好象在输出section描述内没有看到)设置 FLAGS标志,该标志用于设置程序段描述的p_flags域。

下面看一个典型的PHDRS设置

示例

PHDRS

{

headers PT_PHDR PHDRS ;

interp PT_INTERP ;

text PT_LOAD FILEHDR PHDRS ;

data PT_LOAD ;

dynamic PT_DYNAMIC ;

}

SECTIONS

{

. = SIZEOF_HEADERS;

.interp : { *(.interp) } :text :interp

.text : { *(.text) } :text

. rodata : { *(.rodata) } /* defaults to :text */

. = . + 0×1000; /* move to a new page in memory */

.data : { *(.data) } :data

.dynamic : { *(.dynamic) } :data :dynamic

}

十、版本号命令

当使用ELF目标文件格式时,连接器支持带版本号的符号。版本号也只限于ELF文件格式。

读者可以发现仅仅在共享库中,符号的版本号属性才有意义。动态加载器使用符号的版本号为应用程序选择共享库内的一个函数的特定实现版本。

可以在连接脚本内直接使用版本号命令,也可以将版本号命令实现于一个特定版本号描述文件(用连接选项–version-script指定该文件)。

该命令的文法如下,

VERSION { version-script-commands }

以下讨论用gcc

10.1. 带版本号的符号的定义(共享库内)

文件b.c内容如下,

int getVersion ()

{

return 1 ;

}

写连接器的版本控制脚本,本例中为 b.lds,内容如下

VER1.0 {

getVersion;

};

VER2.0{

};

$gcc -c b.c

$gcc -shared - Wl , --version-script = b.lds -o libb.so b.o

可以在 {}内填入要绑定的符号,本例中 getVersion符号就与VER1.0绑定了。

那么如果有一个应用程序连接到该库的 getVersion符号,那么它连接的就是VER1.0版本的 getVersion符号

如果我们对b.c文件进行了升级,更改如下:

int getVersion ()

{

return 101;

}

这里我对getVersion()进行了更改,其返回值的意义也进行改变,也就是它和前不兼容:

为了程序的安全,我们把b.lds更改为,

VER1.0 {

};

VER2.0 {

getVersion;

};

然后生成新的libb.so文件。

这时如果我们运行app.exe(它已经连接到VER1.0版本的 getVersion ()),就会发现该应用程序不能运行了。

提示信息如下:

./app.exe: relocation error: ./app.exe: symbol getVersion, version VER1.0 not defined in file libb.so with link time reference

因为库内没有VER1.0版本的 getVersion () ,只有VER2.0版本的 getVersion () 。

10.2、参看连接的符号的版本

对上面生成的app.exe执行以下命令:

nm app.exe | grep getVersion

结果

U new_true@@VER1.0

用nm命令发现app连接到VER1.0版本的getVersion

10.3、 GNU的扩充

在GNU中,允许在程序文件内绑定 *符号* 到 *带版本号的别名符号*

文件b.c内容如下,

int old_getVersion ()

{

return 1;

}

int new_getVersion ()

{

return 101;

}

__asm__ (".symver old_getVersion , getVersion @ VER1.0 ");

__asm__ (".symver new_getVersion , getVersion @@ VER2.0 ");

其中,对于 VER1.0版本号的 getVersion别名符号是 old_getVersion;

对于VER2.0版本号的getVersion别名符号是new _getVersion ,

在连接时,默认的版本号为 VER2.0

供连接器用的版本控制脚本b.lds内容如下,

VER1.0 {

};

VER2.0 {

};

版本控制文件内必须包含版本 VER1.0 和版本 VER2.0 的定义,因为在b.c文件内有对他们的引用

再次执行以下命令编译连接b.c和app.c

gcc -c src/b.c

gcc -shared -Wl,--version-script=./lds/b.lds -o libb.so b.o

gcc -o app.exe ./src/app.c libb.so

运行:

./app.exe

结果:

Version=0x65

说明app.exe的确是连接的 VER2.0的 getVersion,即 new_getVersion ()

我们再对app.c进行修改,以使它连接的 VER1.0的 getVersion,即 old _getVersion ()

app.c文件:

#include <stdio.h>

__asm__ (".symver getVersion , getVersion@VER1.0 ");

extern int getVersion() ;

int main()

{

printf("Version=%p\n", getVersion() );

return 0;

}

再次编译连接b.c和app.c

运行:

./app.exe

结果:

Version=0x1

说明此次app.exe的确是连接的 VER1.0的 getVersion,即 old _getVersion ()

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这