Oracle® Solaris 11.2 链接程序和库指南

退出打印视图

更新时间: 2014 年 7 月
 
 

复制重定位

共享目标文件通常使用与位置无关的代码生成。对此类型代码的外部数据项的引用通过一组表实现间接寻址。有关更多详细信息,请参见与位置无关的代码。在运行时,将使用数据项的实际地址更新这些表。使用这些已更新的表,无需修改代码本身即可访问数据。

但是,动态可执行文件通常并不使用与位置无关的代码创建。它们所执行的任何外部数据引用看似只能在运行时通过修改执行引用的代码来实现。应避免修改只读文本段。可以使用复制重定位技术来解决此引用。

假设使用链接编辑器创建动态可执行文件,并且发现对数据项的引用位于其中一个相关共享目标文件中。将在动态可执行文件的 .bss 中分配空间,空间的大小等于共享目标文件中的数据项的大小。还为此空间指定在共享目标文件中定义的符号名称。分配此数据时,链接编辑器会生成特殊的复制重定位记录,指示运行时链接程序将数据从共享目标文件复制到动态可执行文件中的已分配空间。

由于指定给此空间的符号为全局符号,因此,使用此符号可以实现任何共享目标文件引用。动态可执行文件可继承数据项。进程中对此项进行引用的任何其他目标文件都绑定到副本。生成此副本所依据的原始数据实际上变成了未使用的数据。

此机制的以下示例使用一组在标准 C 库中维护的系统错误消息。在 SunOS 操作系统早期发行版中,通过两个全局变量 sys_errlist[]sys_nerr 提供此信息接口。第一个变量提供错误消息字符串数组,而第二个变量告知数组本身的大小。这些变量通常按照以下方式在应用程序中使用:

$ cat foo.c
extern int sys_nerr;
extern char *sys_errlist[];

char *
error(int errnumb)
{
        if ((errnumb < 0) || (errnumb >= sys_nerr))
                return (0);
        return (sys_errlist[errnumb]);
}

应用程序使用 error 函数提供焦点以获取与编号 errnumb 关联的系统错误消息。

对使用此代码生成的动态可执文件进行检查,可以更详细地显示复制重定位的实现。

$ cc -o prog main.c foo.c
$ elfdump -sN.dynsym prog | grep ' sys_'
    [24]   0x21240   0x260  OBJT GLOB  D    1 .bss           sys_errlist
    [39]   0x21230     0x4  OBJT GLOB  D    1 .bss           sys_nerr
$ elfdump -c prog
....
Section Header[19]:  sh_name: .bss
    sh_addr:      0x21230         sh_flags:   [ SHF_WRITE SHF_ALLOC ]
    sh_size:      0x270           sh_type:    [ SHT_NOBITS ]
    sh_offset:    0x1230          sh_entsize: 0
    sh_link:      0               sh_info:    0
    sh_addralign: 0x8
....
$ elfdump -r prog

Relocation Section:  .SUNW_reloc
      type                     offset     addend  section        symbol
  ....
  R_SPARC_COPY                0x21240          0  .SUNW_reloc    sys_errlist
  R_SPARC_COPY                0x21230          0  .SUNW_reloc    sys_nerr
  ....

链接编辑器已在动态可执行文件的 .bss 中分配了空间,以便接收由 sys_errlistsys_nerr 表示的数据。这些数据是运行时链接程序在进程初始化时从 C 库中复制。因此,每个使用这些数据的应用程序都在其自己的数据段中获取数据的专用副本。

此技术存在两个缺点。第一,每个应用程序都会由于运行时产生的复制数据开销而降低了性能。第二,数据数组 sys_errlist 的大小现在已成为 C 库接口的一部分。假设要更改此数组的大小,则可能是因为添加了新的错误消息。任何引用此数组的动态可执行文件都必须进行新的链接编辑,以便可以访问所有新错误消息。如果不进行这种新的链接编辑,则动态可执行文件中的已分配空间不足以包含新的数据。

如果动态可执行文件所需的数据由功能接口提供,则不会存在这些缺点。ANSI C 函数 strerror(3C) 基于提供给它的错误号返回指向相应错误字符串的指针。此函数的一种实现可能如下所示:

$ cat strerror.c
static const char *sys_errlist[] = {
    "Error 0",
    "Not owner",
    "No such file or directory",
    ....
};
static const int sys_nerr = sizeof (sys_errlist) / sizeof (char *);

char *
strerror(int errnum)
{
        if ((errnum < 0) || (errnum >= sys_nerr))
                return (0);
        return ((char *)sys_errlist[errnum]);
}

现在,可以将 foo.c 中的错误例程简化为使用此功能接口。通过这种简化,无需在进程初始化时执行原始复制重定位。

此外,由于数据现在对于共享目标文件而言是局部数据,因此数据不再是其接口的一部分。因此,共享目标文件可以灵活地更改数据,而不会对任何使用此数据的动态可执行文件造成不良影响。通常,从共享目标文件接口中删除数据项会改善性能,同时使得共享接口和代码更易于维护。

ldd(1)–d–r 选项一起使用时,可以检验动态可执行文件中存在的任何复制重定位。

例如,假设动态可执行文件 prog 最初根据共享目标文件 libfoo.so.1 生成,并且已记录以下两个复制重定位。

$ cat foo.c
int _size_gets_smaller[16];
int _size_gets_larger[16];
$ cc -o libfoo.so -G foo.c
$ cc -o prog main.c -L. -R. -lfoo
$ elfdump -sN.symtab prog | grep _size
    [49]   0x211d0   0x40  OBJT GLOB  D    0 .bss           _size_gets_larger
    [59]   0x21190   0x40  OBJT GLOB  D    0 .bss           _size_gets_smaller
$ elfdump -r prog | grep _size
  R_SPARC_COPY                0x211d0          0  .SUNW_reloc    _size_gets_larger
  R_SPARC_COPY                0x21190          0  .SUNW_reloc    _size_gets_smaller

以下是此共享目标文件的新版本,其中包含这些符号的不同数据大小。

$ cat foo2.c
int _size_gets_smaller[4];
int _size_gets_larger[32];
$ cc -o libfoo.so -G foo2.c
$ elfdump -sN.symtab libfoo.so | grep _size
    [37]   0x105cc   0x10  OBJT GLOB  D    0 .bss           _size_gets_smaller
    [41]   0x105dc   0x80  OBJT GLOB  D    0 .bss           _size_gets_larger

针对此动态可执行文件运行 ldd(1) 将显示以下内容:

$ ldd -d prog
    libfoo.so.1 =>   ./libfoo.so.1
    ....
    relocation R_SPARC_COPY sizes differ: _size_gets_larger
        (file prog size=0x40; file ./libfoo.so size=0x80)
        prog size used; possible data truncation
    relocation R_SPARC_COPY sizes differ: _size_gets_smaller
        (file prog size=0x40; file ./libfoo.so size=0x10)
        ./libfoo.so size used; possible insufficient data copied
....

ldd(1) 显示此动态可执行文件将复制此共享目标文件必须提供的所有数据,但只接受其已分配空间所允许的数据量。

通过根据与位置无关的代码生成应用程序可以删除复制重定位。请参见与位置无关的代码