符号解析的方式很广,有简单直观的,也有错综复杂的。大多数解析由链接编辑器执行,且没有任何提示。但是,某些重定位可能伴随有警告诊断,而某些可能导致致命错误状态。
最常见的简单解析方式需要将一个目标文件中的符号引用与另一个目标文件中的符号定义绑定起来。此种绑定可用于两个可重定位目标文件之间,也可用于一个可重定位目标文件与在共享目标文件依赖项中找到的第一个定义之间。复杂解析方式通常用于两个或多个可重定位目标文件之间。
这两种符号解析方式取决于符号的属性、提供符号的文件类型以及要生成的文件类型。有关符号属性的完整说明,请参见符号表节。但是,对于以下论述,标识了三种基本符号类型。
形式最简单的符号解析需要使用优先级关系。此关系中,已定义符号优先于暂定符号,而暂定符号又优先于未定义符号。
以下 C 代码示例说明如何生成这些符号类型。未定义符号使用 u_ 作为前缀。暂定符号使用 t_ 作为前缀。已定义符号使用 d_ 作为前缀。
$ cat main.c extern int u_bar; extern int u_foo(); int t_bar; int d_bar = 1; int d_foo() { return (u_foo(u_bar, t_bar, d_bar)); } $ cc -o main.o -c main.c $ elfdump -s main.o Symbol Table Section: .symtab index value size type bind oth ver shndx name .... [7] 0 0 FUNC GLOB D 0 UNDEF u_foo [8] 0x10 0x40 FUNC GLOB D 0 .text d_foo [9] 0x4 0x4 OBJT GLOB D 0 COMMON t_bar [10] 0 0x4 NOTY GLOB D 0 UNDEF u_bar [11] 0 0x4 OBJT GLOB D 0 .data d_bar
简单符号解析是迄今最常见的解析方式。在这种情况下,将检测两个具有类似特征的符号,一个符号优先于另一个符号。此符号解析由链接编辑器执行,且没有任何提示。例如,对于具有相同绑定的符号,一个文件中的符号引用将绑定到另一个文件中的已定义或暂定符号定义。或者,一个文件中的暂定符号定义将绑定到另一个文件中的已定义符号定义。此种解析方式可用于两个可重定位目标文件之间,也可用于一个可重定位目标文件与在共享目标文件依赖项中找到的第一个定义之间。
要解析的符号可以具有全局绑定或弱绑定。处理可重定位目标文件时,弱绑定的优先级低于全局绑定。弱符号定义将在不给出任何提示的情况下被同一名称的全局定义所覆盖。
在可重定位目标文件与共享目标文件之间或者多个共享目标文件之间,还有一种简单符号解析方式,即插入。在这些情况下,如果多重定义了某个符号,链接编辑器会采用可重定位目标文件或多个共享目标文件之间的第一个定义,且不作任何提示。可重定位目标文件的定义或第一个共享目标文件的定义会在其他所有定义上插入。这种插入可用于覆盖其他共享目标文件提供的功能。可重定位目标文件与共享目标文件之间或多个共享目标文件之间的多重定义符号采用同样的处理方式。符号是弱绑定还是全局绑定无关紧要。无论哪种符号绑定,链接编辑器和运行时链接程序都行为一致,将其解析为第一个定义。
使用链接编辑器的 –m 选项可将所有插入的符号引用的列表和节装入地址信息写入到标准输出中。
如果发现两个符号的名称相同,但属性不同,则将进行复杂解析。在这些情况下,链接编辑器将选择最适合的符号同时生成一条警告消息。此消息指出符号、发生冲突的属性以及包含符号定义的文件的标识。在以下示例中,包含数据项数组定义的两个文件有不同的大小要求。
$ cat foo.c int array[1]; $ cat bar.c int array[2] = { 1, 2 }; $ ld -r -o temp.o foo.c bar.c ld: warning: symbol 'array' has differing sizes: (file foo.o value=0x4; file bar.o value=0x8); bar.o definition taken
如果符号的对齐要求不同,则会产生一条类似的诊断消息。在这两种情况下,使用链接编辑器的 –t 选项可以不进行诊断。
另一种形式的属性差异是符号的类型。在以下示例中,符号 bar() 已同时定义为数据项和函数。
$ cat foo.c int bar() { return (0); } $ cc -o libfoo.so -G -K pic foo.c $ cat main.c int bar = 1; int main() { return (bar); } $ cc -o main main.c -L. -lfoo ld: warning: symbol 'bar' has differing types: (file main.o type=OBJT; file ./libfoo.so type=FUNC); main.o definition taken
在类似以上示例的情况下,在可重定位目标文件与共享目标文件之间进行解析时将采用可重定位目标文件定义。或者,在两个共享目标文件之间进行解析时采用第一个定义。在弱绑定符号或全局绑定符号之间进行这种解析时,还会生成警告。
无法解析的符号冲突会导致致命错误状态,并生成相应的错误消息。此消息会指示符号名称以及提供符号的文件的名称。不会生成任何输出文件。虽然致命状态足以导致链接编辑终止,但会先完成所有输入文件处理。通过这种方式,所有致命解析错误都可识别。
当两个可重定位目标文件都定义相同名称的非弱符号时,就会出现最常见的致命错误状态。
$ cat foo.c int bar = 1; $ cat bar.c int bar() { return (0); } $ ld -r -o temp.o foo.c bar.c ld: fatal: symbol `bar' is multiply-defined: (file foo.o and file bar.o);
对于符号 bar 来讲,foo.c 和 bar.c 的定义相冲突。因为链接编辑器无法确定哪个符号优先,所以链接编辑通常会终止,并生成一条错误消息。可以使用链接编辑器的 –z muldefs 选项抑制出现此错误状态。此选项允许采用第一个符号定义。