前晚写了一段代码,用于测试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

Tags:
骇客帝国 | 评论(1) | 引用(0) | 阅读(1373)
vps观察者 Homepage
2009/07/30 03:39
在linux下编译我一直很头疼
分页: 1/1 第一页 1 最后页
发表评论
表情
emotemotemotemotemot
emotemotemotemotemot
emotemotemotemotemot
emotemotemotemotemot
emotemotemotemotemot
打开HTML
打开UBB
打开表情
隐藏
昵称   密码   游客无需密码
网址   电邮   [注册]