Board logo

标题: 控制共享库的符号可见性-符号可见性简介(4) [打印本页]

作者: look_w    时间: 2018-1-8 15:27     标题: 控制共享库的符号可见性-符号可见性简介(4)

符号抢占正如前面所提到的,可见性关键字 export 和 protected 之间存在微妙的区别。此微妙区别就在于符号抢占(symbol preemption)上。符号抢占出现在当链接时解析的符号地址被另一个在运行时解析的符号地址取代时(注意,尽管在 AIX 上运行时链接是可选的)。从概念上讲,运行时链接会在程序执行开始之后解析共享模块中未定义和非延迟的符号。符号抢占是一种提供运行时定义(这些函数定义在链接时不可用)和符号重新绑定功能的机制。在 AIX 上,当主程序利用 -brtl 标志进行链接时或者当预加载的库利用 LDR_CNTRL 环境变量进行指定时,程序能够使用运行时链接设施。利用 -brtl 进行编译会向程序添加一个对动态链接器的引用,当程序开始运行时,该引用会被程序的启动代码 (/lib/crt0.o) 调用。共享对象输入文件按其在命令行中指定的相同顺序在程序加载器部分被列出为关联项。当程序开始运行时,系统加载器加载这些共享对象,以便它们的定义对动态链接器可用。
因此,在运行时重新定义共享对象中的条目是一种叫做符号抢占的功能。 符号抢占只有在 AIX 上使用运行时链接时才能发挥作用。在链接时绑定到一个模块的导入会在运行时重新绑定到另一个模块。一个局部定义是否可以被导入的实例抢占,取决于模块的链接方式。然而,非导出符号永远不会在运行时被抢占。运行时加载器加载组件时,该组件中所有具有默认可见性的符号都会被已经加载的组件中相同名称的符号抢占。注意,因为主程序映像总是最先加载的,所以其定义的任何符号都不会被抢占(重新定义)。
受保护符号会被导出,但是不可以被抢占。相反,导出的符号可被导出并抢占(如果使用运行时链接的话)。
对于默认符号,Linux® 和 AIX 之间存在差别。GNU 编译器和 ELF 文件格式定义一种默认可见性,用于可被导出和抢占的符号。这类似于 AIX 上定义的 exported 可见性。
下面的代码以 AIX 平台为例:
清单 3. func.C
1
2
3
4
5
6
7
8
#include <stdio.h>
void func_DEFAULT(){
        printf("func_DEFAULT in the shared library, Not preempted\n");
}

void func_PROC(){
        printf("func_PROC in the shared library, Not preempted\n");
}




清单 4. invoke.C
1
2
3
4
5
6
7
extern void func_DEFAULT();
extern void func_PROC();

void invoke(){
        func_DEFAULT();
        func_PROC();
}




清单 5. main.C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

extern void func_DEFAULT();
extern void func_PROC();
extern void invoke();

int main(){
        invoke();
        return 0;
}

void func_DEFAULT(){
        printf("func_DEFAULT redefined in main program, Preempted ==> EXP\n");
}

void func_PROC(){
        printf("func_PROC redefined in main program, Preempted ==> EXP\n");
}




在上面的描述中,我们在 func.C 和 main.C 中都定义了 func_DEFAULT 和 func_PROC。它们名称相同,但是行为不同。来自 invoke.C 的函数 invoke 将依次调用 func_DEFAULT 和 func_PROC。我们将使用下面的 exportlist 代码来看符号是否被导出,以及是如何导出的。
清单 6. exportlist
1
2
3
func_DEFAULT__Fv export
func_PROC__Fv protected
invoke__Fv




如果使用的是 AIX 6.1 之前的链接器版本,可以使用空格代替  export,symbolic 关键字代替 protected 关键字。下面代码中列出了构建 libtest.so 库和 main 可执行文件的命令:
1
2
3
4
5
6
7
8
9
10
/* generate position-independent code suitable for use in shared libraries. */
$ xlC -c func.C invoke.C -qpic

/* generate shared library, exportlist is used to control symbol visibility */
$ xlC -G -o libtest.so func.o invoke.o -bE:exportlist

$ xlC -c main.C

/* -brtl enable runtime linkage. */
$ xlC main.o -L. -ltest -brtl -bexpall -o main




本质上,我们是从 func.o 和 invoke.o 构建 libtest.so。我们使用 exportlist 来将 func.C 中的 func_DEFAULT 和 func.C 中的 func_PROC 设置为导出符号,但是仍然是受保护的。这样,libtest.so 就有两个导出符号和一个受保护符号。对于主程序,我们从 main.C 导出所有符号,但是将它链接到 libtest.so。注意,我们使用 -brtl 标志来为 libtest.so 启用动态链接。
下一步是调用主程序。
1
2
3
$ ./main
func_DEFAULT redefined in main program, Preempted ==> EXP
func_PROC in the shared library, Not preempted




在这里我们看到一些有趣的东西:func_DEFAULT是来自 main.C 的版本,而 func_PROC 是来自 libtest.so (func.C) 的版本。func_DEFAULT 符号被抢占,因为来自 libtest.so 的局部版本(我们说它是局部的,是因为调用函数 invoke 来自于 invoke.C,后者本质上与来自 func.C 的 func_DEFAULT 位于同一模块)被来自另一个模块的 func_DEFAULT 符号所取代。然而,func_PROC 上确实出现了相同的条件,它在导出文件中被指定为 protected 可见性。
注意,可以抢占其他符号的符号应该总是导出符号。假设我们在构建可执行文件 main 时删除了 -bexpall 选项,那么输出如下所示:
1
2
3
4
$ xlC main.o -L. -ltest -brtl -o main; //-brtl enable runtime linkage.
$ ./main
func_DEFAULT in the shared library, Not preempted
func_PROC in the shared library, Not preempted




这里没有发生抢占。所有符号都保持模块中的相同版本。
实际上,要在运行时检查符号是否是导出符号或者受保护符号,我们可以使用 dump 实用工具:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ dump -TRv libtest.so
libtest.so:

                        ***Loader Section***

                        ***Loader Symbol Table Information***
[Index]      Value      Scn     IMEX Sclass   Type           IMPid Name

[0]     0x00000000    undef      IMP     DS EXTref   libc.a(shr.o) printf
[1]     0x2000040c    .data      EXP     DS SECdef        [noIMid] func_DEFAULT__Fv
[2]     0x20000418    .data      EXP     DS SECdef        [noIMid] func_PROC__Fv
[3]     0x20000424    .data      EXP     DS SECdef        [noIMid] invoke__Fv

                        ***Relocation Information***
             Vaddr      Symndx      Type      Relsect    Name
        0x2000040c  0x00000000   Pos_Rel      0x0002     .text
        0x20000410  0x00000001   Pos_Rel      0x0002     .data
        0x20000418  0x00000000   Pos_Rel      0x0002     .text
        0x2000041c  0x00000001   Pos_Rel      0x0002     .data
        0x20000424  0x00000000   Pos_Rel      0x0002     .text
        0x20000428  0x00000001   Pos_Rel      0x0002     .data
        0x20000430  0x00000000   Pos_Rel      0x0002     .text
        0x20000434  0x00000003   Pos_Rel      0x0002     printf
        0x20000438  0x00000004   Pos_Rel      0x0002     func_DEFAULT__Fv
        0x2000043c  0x00000006   Pos_Rel      0x0002     invoke__Fv




这是来自 libtest.so 的输出。我们可以发现,func_DEFAULT__Fv 和 func_PROC__Fv 都是导出符号。然而,func_PROC__Fv 不具有任何重新定位。这意味着,加载器可能找不到方法来替换 TOC 表中 func_PROC 的地址。TOC 表中 func_PROC 的地址是函数调用要将控制转移到的地方。因此,func_PROC 似乎不会被抢占。我们然后认识到,它是受保护的
实际上,符号抢占使用得很少。然而,它让我们可以在运行时动态地替换符号,但是也会留下一些安全漏洞。如果不想让库中的关键符号被抢占(但是仍然需要导出),为安全起见,需要将它设置为受保护的。




欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) Powered by Discuz! 7.0.0