标题:链接静态库和动态库对符号处理的一些测试(4m/1,2/2) 出处:Wirlfly's Blog 时间:Mon, 01 Jun 2009 13:37:44 +0000 作者:wirlfly 地址:http://wf.xplore.cn/post/test_lib.php 内容: 前晚写了一段代码,用于测试Linux下典型的静态库和动态库对全局符号的不同处理.作为Linux C/CPP程序员,一般对静态库和动态库的使用都十分频繁,虽然也出现过静态库链接顺序导致符号无法解析的错误.但是大体理解静态库和动态库并不困难,但是真正理解静态库和动态库在链接和(或)加载过程中的原理,还是得深入阅读一些文档,写一些测试代码才能有直观的掌握. 如果您对测试过程不太感兴趣(确实这个过程比较乏味),请跳跃到最后一段,直接看结论即可. 给出测试代码(文末给出源码包,遵循GPL发布): 6个源代码文件(test_d.c,test.h,test_lib.c,test_lib.h,test_o.c,test_s.c). 1个Makefile(文末给出Makefile的内容,可以当作一个模板使用). 生成1个动态库文件(libtest_d.so),1个静态库文件(libtest_s.a),3个可执行目标文件(test_lib_d,test_lib_s,test_lib_o). 还有其他3个和源码文件(*.c)相对应的可重定位目标文件(*.o). 引用 [yhzh@localhost 20090530]$ ls Makefile test_d.c test.h test_lib.c test_lib.h test_o.c test_s.c [yhzh@localhost 20090530]$ ls libtest_d.so Makefile test_d.o test_lib.c test_lib.h test_lib.o test_o.c test_s.c libtest_s.a test_d.c test.h test_lib_d test_lib_o test_lib_s test_o.o test_s.o 动态库的生成, 源码test_d.c, 定义了两个全局函数,自增一个静态变量和打印变量之值. 生成方法: gcc -o libtest_d.so test_d.o -Wl,-rpath,/home/yhzh/code/c/test/20090530 -L/usr/local/lib -L/home/yhzh/tools/lib -L. -shared 引用 [yhzh@localhost 20090530]$ cat test_d.c #include "test.h" #include "test_lib.h" static int a = 0; int test_inc() { fprintf(stdout, "[d]Begin in %s, static a is %d\n", __FILE__, a); a++; fprintf(stdout, "[d]End in %s, static a is %d\n", __FILE__, a); return a; } void print_d() { fprintf(stdout, "[d]In %s[%s], static a is %d\n", __FILE__, __func__, a); return; } void addition_d() { fprintf(stdout, "[d]In %s[%s], addition print\n", __FILE__, __func__); return; } 静态库的生成, 源码test_s.c, 和动态库的测试方法一致,定义了两个全局函数,自增一个静态变量和打印变量之值. 生成方法: ar ru libtest_s.a test_s.o && ranlib libtest_s.a 引用 [yhzh@localhost 20090530]$ cat test_s.c #include "test.h" #include "test_lib.h" static int a = 0; /* add __attribute__((weak)) here? */ int test_inc() { fprintf(stdout, "[s]Begin in %s, static a is %d\n", __FILE__, a); a++; fprintf(stdout, "[s]End in %s, static a is %d\n", __FILE__, a); return a; } void print_s() { fprintf(stdout, "[s]In %s[%s], static a is %d\n", __FILE__, __func__, a); return; } void addition_s() { fprintf(stdout, "[s]In %s[%s], addition print\n", __FILE__, __func__); return; } 源码test_lib.c, 调用同名的自增函数, 调用打印动态库函数, 调用或者不调用打印静态库函数. 生成两个可执行目标文件的方法: gcc -o test_lib_d test_lib.o -Wl,-rpath,/home/yhzh/code/c/test/20090530 -L/usr/local/lib -L/home/yhzh/tools/lib -L. -ltest_d -ltest_s gcc -o test_lib_s test_lib.o -Wl,-rpath,/home/yhzh/code/c/test/20090530 -L/usr/local/lib -L/home/yhzh/tools/lib -L. -ltest_s -ltest_d 以不同顺序加载静态库和动态库生成可执行目标文件. 引用 [yhzh@localhost 20090530]$ cat test_lib.c #include "test.h" #include "test_lib.h" int main(int argc, char **argv) { int a = 0; a = test_inc(); print_d(); print_s(); fprintf(stdout, "[1]after test_inc, a is %d\n", a); a = test_inc(); print_d(); print_s(); fprintf(stdout, "[2]after test_inc, a is %d\n", a); return 0; } A部分: 运行生成的test_lib_d 和 test_lib_s, 可以得到如下结果: 引用 [yhzh@localhost 20090530]$ ./test_lib_d [s]Begin in test_s.c, static a is 0 [s]End in test_s.c, static a is 1 [d]In test_d.c[print_d], static a is 0 [s]In test_s.c[print_s], static a is 1 [1]after test_inc, a is 1 [s]Begin in test_s.c, static a is 1 [s]End in test_s.c, static a is 2 [d]In test_d.c[print_d], static a is 0 [s]In test_s.c[print_s], static a is 2 [2]after test_inc, a is 2 [yhzh@localhost 20090530]$ ./test_lib_s [s]Begin in test_s.c, static a is 0 [s]End in test_s.c, static a is 1 [d]In test_d.c[print_d], static a is 0 [s]In test_s.c[print_s], static a is 1 [1]after test_inc, a is 1 [s]Begin in test_s.c, static a is 1 [s]End in test_s.c, static a is 2 [d]In test_d.c[print_d], static a is 0 [s]In test_s.c[print_s], static a is 2 [2]after test_inc, a is 2 这个结果似乎很出乎意料, 为什么首先加载动态库时, 调用test_inc()也是进入了静态库中定义的函数中呢? B部分: 当注释掉test_lib.c中的两行print_s(): 引用 11 //print_s(); ... 16 //print_s(); 然后再编译运行,可以得到如下结果: 引用 [yhzh@localhost 20090530]$ ./test_lib_d [d]Begin in test_d.c, static a is 0 [d]End in test_d.c, static a is 1 [d]In test_d.c[print_d], static a is 1 [1]after test_inc, a is 1 [d]Begin in test_d.c, static a is 1 [d]End in test_d.c, static a is 2 [d]In test_d.c[print_d], static a is 2 [2]after test_inc, a is 2 [yhzh@localhost 20090530]$ ./test_lib_s [s]Begin in test_s.c, static a is 0 [s]End in test_s.c, static a is 1 [d]In test_d.c[print_d], static a is 0 [1]after test_inc, a is 1 [s]Begin in test_s.c, static a is 1 [s]End in test_s.c, static a is 2 [d]In test_d.c[print_d], static a is 0 [2]after test_inc, a is 2 这个结果才是意料之中(可以看运行结果,也可以看链接之后的可执行目标文件的大小).要理解这样的结果,必须理解链接器在链接静态库和动态库的过程中处理符号表的原理.如果能理解在运行可执行目标文件时,加载器的工作原理会更完美. 对于定义在多处的同样的全局符号名(例如分别定义在静态库中,动态库中和可重定位目标文件),在链接器链接过程中: 对于动态库中的全局符号是这样处理的: 1. 将动态库中一些符号信息和重定位信息拷贝进新目标文件的符号表(代码和数据都不处理).选择需要拷贝的符号是在新目标文件的符号表中未知的. 2. 将从动态库中引入的符号表中的head index域设置为UNDEF,value设置为0(可以通过readelf -s/nm/objdump -t查看),这样做的目的是,方便装载器运行时可以解析动态库文件中的代码和数据引用. 对于静态库中的全局符号是这样处理的: 1. 先扫描新目标文件的符号表中未定义域(U域),寻找定义在静态库中的符号信息. 2. 如果存在某个符号信息或者重定位信息相对应,可以让未定义引用的符号从U域转移出来的话,则拷贝整个静态库的全局符号信息和重定位信息过来,并做head index和value调整. 3. 如果不存在任何一个符号信息或者重定位信息相对应,则放弃拷贝静态库的任何内容. 因此,在链接器同时处理既有可重定位目标文件,静态库文件和动态库文件时: 1. 在静态库文件之间,链接顺序可以会导致未知符号错误(连接完毕之后,U域不为空),解决办法是调整静态库顺序,或交叉链接相互依赖的静态库. 2. 在静态库和动态库文件之间,这个关系比较复杂.如果链接动态库在前,而静态库在后, 那么只要静态库中没有U域中相应的任何一个全局符号,也就是链接器主动放弃操作静态库, 那么定义在动态库中的全局符号能得到保存;一旦存在任何一个全局的符号, 动态库中的全局同名符号, 会被静态库中的符号覆盖, 不管是否在静态库中强制设置了weak BIND属性,这个属性只对可重定位目标文件有效(这里不列出,源码包了也做了这个测试). 如果静态库在前,动态库在后,直接按流程链接即可. 总之,在Linux平台(2.6.25/29),在加载多处定义的全局符号是,静态库优先级似乎高于动态库.如果对此感兴趣,可以参考< CS:APP >的第七章和< LINKER and LOADER >全书. 附Makefile: 引用 ## ## Copyright (C) 2008 Spark Zheng ## ## TAKE AS a common prototype of Makefile ## PREFIX ?= /home/yhzh/tools PROJECT_HOME ?= /home/yhzh/code/c/test/20090530 export PREFIX export PROJECT_HOME CC = gcc CXX = g++ CFLAGS = -g -W -Wall -fPIC CXXFLAGS = -g -W -Wall -fPIC CFLAGS_WARN = -Wshadow -Wcast-qual -Winline -Wunreachable-code \ -Wredundant-decls -Wmissing-prototypes \ -Wstrict-prototypes -Wnested-externs CXXFLAGS_WARN = CFLAGS_PREVD = -D_DEBUG CXXFLAGS_PREVD = -D_DEBUG CFLAGS_OPT = -O2 -fomit-frame-pointer CXXFLAGS_OPT = -O2 INCFLAGS := -I. -I./include -I.. -I../include -I$(PREFIX)/include LDFLAGS := -Wl,-rpath,$(PROJECT_HOME) \ -L/usr/local/lib -L$(PREFIX)/lib -L. LIB_LDFLAGS := $(LDFLAGS) -shared LIBS = -lpthread -levent RANLIB = ranlib AR = ar ARFLAGS = ru INSTALL ?= install -c UNINSTALL ?= rm -f DIRS = DIRS += TARGETS = $(EXEC_TARGETS) $(LIB_TARGETS) EXEC_TARGETS = test_lib_d test_lib_s test_lib_o LIB_TARGETS = libtest_d.so libtest_s.a LIB_HEADS = OBJECTS = $(EXEC_OBJECTS) $(LIB_OBJECTS) EXEC_OBJECTS = test_lib.o LIB_OBJECTS = test_d.o test_s.o test_o.o all: $(TARGETS) test_lib_d: $(EXEC_OBJECTS) $(LIB_TARGETS) set -e; for i in $(DIRS); do \ echo "make all enter $$i."; \ make -C $$i; \ echo "make all leave $$i."; \ done; $(CC) -o $@ $(EXEC_OBJECTS) $(LDFLAGS) -ltest_d -ltest_s test_lib_s: $(EXEC_OBJECTS) $(LIB_TARGETS) set -e; for i in $(DIRS); do \ echo "make all enter $$i."; \ make -C $$i; \ echo "make all leave $$i."; \ done; $(CC) -o $@ $(EXEC_OBJECTS) $(LDFLAGS) -ltest_s -ltest_d test_lib_o: $(EXEC_OBJECTS) $(LIB_TARGETS) test_o.o set -e; for i in $(DIRS); do \ echo "make all enter $$i."; \ make -C $$i; \ echo "make all leave $$i."; \ done; $(CC) -o $@ $(EXEC_OBJECTS) $(LDFLAGS) -ltest_s -ltest_d test_o.o libtest_d.so: test_d.o $(CC) -o $@ $< $(LIB_LDFLAGS) libtest_s.a: test_s.o $(AR) $(ARFLAGS) $@ $< $(RANLIB) $@ .SUFFIXES: .SUFFIXES: .c .cc .C .cpp .o .c.o : $(CC) -o $@ -c $< $(CFLAGS) $(CFLAGS_WARN) $(CFLAGS_PREVD) $(INCFLAGS) count: -wc *.c *.cc *.C *.cpp *.h *.hpp set -e; for i in $(DIRS); do \ echo "make count enter $$i."; \ make -C $$i count; \ echo "make count leave $$i."; \ done; clean: -rm -f $(OBJECTS) $(TARGETS) *.swp *~ core.* set -e; for i in $(DIRS); do \ echo "make clean enter $$i."; \ make -C $$i clean; \ echo "make clean leave $$i."; \ done; install: ifneq ("$(EXEC_TARGETS)","") $(INSTALL) $(EXEC_TARGETS) $(PREFIX)/bin/ endif ifneq ("$(LIB_TARGETS)","") $(INSTALL) $(LIB_TARGETS) $(PREFIX)/lib/ endif ifneq ("$(LIB_HEADS)","") $(INSTALL) $(LIB_HEADS) $(PREFIX)/include/ endif set -e; for i in $(DIRS); do \ echo "make install enter $$i."; \ make -C $$i install; \ echo "make intall leave $$i."; \ done; uninstall: ifneq ("$(EXEC_TARGETS)","") set -e; for i in $(EXEC_TARGETS); do \ $(UNINSTALL) $(PREFIX)/bin/$$i; \ done; endif ifneq ("$(LIB_TARGETS)","") set -e; for i in $(LIB_TARGETS) do \ $(UNINSTALL) $(PREFIX)/lib/$$i; \ done; endif ifneq ("$(LIB_HEADS)","") set -e; for i in $(LIB_HEADS) do \ $(UNINSTALL) $(PREFIX)/include/$$i; \ done; endif set -e; for i in $(DIRS); do \ echo "make uninstall enter $$i."; \ make -C $$i uninstall; \ echo "make uninstall leave $$i."; \ done; .PHONY: all .PHONY: count .PHONY: clean .PHONY: install .PHONY: uninstall 点击这里下载文件 Generated by Bo-blog 2.1.1 Release