文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>从程序员角度看ELF(2)

从程序员角度看ELF(2)

时间:2007-04-09  来源:Echo CHEN

★4 ELF的动态连接与装载 

★4.1 动态连接 

当在UNIX系统下,用C编译器把C源代码编译成可执行文件时,c编译驱动器一般 
将调用C的预处理,编译器,汇编器和连接器。 

.     c编译驱动器首先把C源代码传到C的预处理器,它以处理过的宏和 
    指示器形式输出纯C语言代码。 

.    c编译器把处理过的C语言代码翻译为机器相关的汇编代码。 

.    汇编器把结果的汇编语言代码翻译成目标的机器指令。结果这些 
    机器指令就被存储成指定的二进制文件格式,在这里,我们使用的 
    ELF格式。 

.    最后的阶段,连接器连接所有的object文件,加入所有的启动代码和 
    在程序中引用的库函数。 

    下面有两种方法使用lib库 
     
    --static library 
    一个集合,包含了那些object文件中包含的library例程和数据。用 
    该方法,连接时连接器将产生一个独立的object文件(这些 
    object文件保存着程序所要引用的函数和数据)的copy。 
     
    --shared library 
    是共享文件,它包含了函数和数据。用这样连接出来的程序仅在可执行 
    程序中存储着共享库的名字和一些程序引用到的标号。在运行时,动态 
    连接器(在ELF中也叫做程序解释器)将把共享库映象到进程的虚拟 
    地址空间里去,通过名字解析在共享库中的标号。该处理过程也称为 
    动态连接(dynamic linking) 

程序员不需要知道动态连接时用到的共享库做什么,每件事情对程序员都是 
透明的。 

★4.2 动态装载(Dynamic Loading) 

动态装载是这样一个过程:把共享库放到执行时进程的地址空间,在库中查找 
函数的地址,然后调用那个函数,当不再需要的时候,卸载共享库。它的执行 
过程作为动态连接的服务接口。 

在ELF下,程序接口通常在<dlfcn.h>中被定义。如下: 

void *dlopen(const char * filename,int flag); 
const char * dlerror(void); 
const void * dlsym (void handle*,const char * symbol); 
int dlclose(void * handle); 

这些函数包含在libdl.so中。下面是个例子,展示动态装载是如何工作的。 
主程序在运行时动态的装载共享库。一方面可指出哪个共享库被使用,哪个 
函数被调用。一方面也能在访问共享库中的数据。 

[alert7@redhat62 dl]# cat dltest.c 
#include <stdio.h> 
#include <stdlib.h> 
#include <getopt.h> 
#include <dlfcn.h> 
#include <ctype.h> 

typedef void (*func_t) (const char *); 

void dltest(const char *s) 

    printf("From dltest:"); 
    for (;*s;s++) 
    {     
        putchar(toupper(*s)); 
    } 
    putchar(‘\n‘); 


main(int argc,char **argv) 

void *handle; 
func_t fptr; 
char * libname = "./libfoo.so"; 
char **name=NULL; 
char *funcname = "foo"; 
char *param= "Dynamic Loading Test"; 
int ch; 
int mode=RTLD_LAZY;     

while ((ch = getopt(argc,argv,"a:b:f:l:"))!=EOF) 

    switch(ch) 
    { 
    case ‘a‘:/*argument*/ 
        param=optarg; 
        break; 
    case ‘b‘:/*how to bind*/ 
        switch(*optarg) 
        { 
        case ‘l‘:/*lazy*/ 
        mode = RTLD_LAZY; 
        break; 
        case ‘n‘:/*now*/ 
        mode = RTLD_NOW; 
        break; 
        } 
        break; 
    case ‘l‘:/*which shared library*/ 
        libname= optarg; 
        break; 
    case ‘f‘:/*which function*/ 
        funcname= optarg; 
    } 
   } 

handle = dlopen(libname,mode); 
if (handle ==NULL) 

fprintf(stderr,"%s:dlopen:‘%s‘\n",libname,dlerror()); 
exit(1); 


fptr=(func_t)dlsym(handle,funcname); 
if (fptr==NULL) 

fprintf(stderr,"%s:dlsym:‘%s‘\n",funcname,dlerror()); 
exit(1); 


name = (char **) dlsym(handle,"libname"); 
if (name==NULL) 

fprintf(stderr,"%s:dlsym:‘libname‘\n",dlerror()); 
exit(1); 


printf("Call ‘%s‘ in ‘%s‘:\n",funcname,*name); 

/*call that function with ‘param‘*/ 
(*fptr)(param); 

dlclose(handle); 
return 0; 



这里有两个共享库,一个是libfoo.so一个是libbar.so。每个都用同样的全局 
字符串变量libname,分别各自有foo和bar函数。通过dlsym,对程序来说,他们 
都是可用的。 

[alert7@redhat62 dl]# cat libbar.c 
#include <stdio.h> 

extern void dltest(const char *); 
const char * const libname = "libbar.so"; 

void bar (const char *s) 

dltest("Called from libbar."); 
printf("libbar:%s\n",s); 


[alert7@redhat62 dl]# cat libfoo.c 
#include <stdio.h> 

extern void dltest (const char *s); 
const char *const libname="libfoo.so"; 

void foo(const char *s) 

    const char *saved=s; 
     
    dltest("Called from libfoo"); 
    printf("libfoo:"); 
    for (;*s;s++); 
    for (s--;s>=saved;s--) 
    { 
    putchar (*s); 
    } 
    putchar (‘\n‘); 


使用Makefile文件来编译共享库和主程序是很有用的。因为libbar.so和 
libfoo.so也调用了主程序里的dltest函数。 

[alert7@redhat62 dl] #cat Makefile 
CC=gcc 
LDFLAGS=-rdynamic 
SHLDFLAGS= 
RM=rm 

all:dltest 

libfoo.o:libfoo.c 
    $(CC) -c -fPIC $< 

libfoo.so:libfoo.o 
    $(CC) $(SHLDFLAGS) -shared -o $@ $^ 

libbar: libbar.c 
    $(CC) -c -fPIC $< 

libbar.so:libbar.o 
    $(CC) $(SHLDFLAGS) -shared -o $@ $^ 

dltest: dltest.o libbar.so libfoo.so 
    $(CC) $(LDFLAGS) -o $@ dltest.o -ldl 

clean: 
    $(RM) *.o *.so dltest 

处理流程: 

[alert7@redhat62 dl]# export ELF_LD_LIBRARY_PATH=. 
[alert7@redhat62 dl]# ./dltest 
Call ‘foo‘ in ‘libfoo.so‘: 
From dltest:CALLED FROM LIBFOO 
libfoo:tseT gnidaoL cimanyD 
[alert7@redhat62 dl]# ./dltest -f bar 
bar:dlsym:‘./libfoo.so: undefined symbol: bar‘ 
[alert7@redhat62 dl]# ./dltest -f bar -l ./libbar.so 
Call ‘bar‘ in ‘libbar.so‘: 
From dltest:CALLED FROM LIBBAR. 
libbar:Dynamic Loading Test 

在动态装载进程中调用的第一个函数就是dlopen,它使得共享可库对 
运行着的进程可用。dlopen返回一个handle,该handle被后面的dlsym 
和dlclose函数使用。dlopen的参数为NULL有特殊的意思---它使得在 
程序导出的标号和当前已经装载进内存的共享库导出的标号通过dlsym 
就可利用。 

在一个共享库已经装载进运行着的进程的地址空间后,dlsym可用来 
获得在共享库中导出的标号地址。然后就可以通过dlsym返回的地址 
来访问里面的函数和数据。 

当一个共享库不再需要使用的时候,就可以调用dlclose卸载该函数库。 
假如共享库在启动时刻或者是通过其他的dlopen调用被装载的话,该 
共享库不会从调用的进程的地址空间被移走。 

假如dlclose操作成功,返回为0。dlopen和dlsym如果有错误,将返回 
为NULL。为了获取诊断信息,可调用dlerror. 

★5 支持ELF的LINUX上的编译器GNU GCC 

感谢Eric Youngdale ([email protected]),lan Lance Taylor ([email protected]
还有许多为gcc支持ELF功能的默默做贡献的人。我们能用gcc和GNU的二进制 
工具很容易的创建ELF可执行文件和共享库。 

★5.1 共享C库 Shared C Library 

在ELF下构造一个共享库比其他的容易的多。但是需要编译器,汇编器, 
连接器的支持。首先,需要产生位置无关(position-independent)代码。 
要做到这一点,gcc需要加上编译选项-fPIC 
[alert7@redhat62 dl]# gcc -fPIC -O -c libbar.c 

这时候就适合构造共享库了,加上-shared编译选项 
[alert7@redhat62 dl]# gcc -shared -o libbar.so libbar.o 

现在我们构造的libbar.so就可以被连接器(link editor)和动态连接器 
(dynamic linker)。只要编译时带上-fPIC编译选项,可以把许多重定位 
文件加到共享库中。为了把baz.o和共享库连接在一起,可以如下操作: 
# gcc -O -c baz.c 
# gcc -o baz baz.o -L. -lbar 

在把libbar.so安装到动态连接器能找到的正确位置上之后,运行baz将 
使libbar.so映象到baz的进程地址空间。内存中libbar.so的一份拷贝将 
被所有的可执行文件(这些可执行程序连接时和它一块儿连接的或者 
在运行时动态装载的)共享。 

★5.2 共享C++库 Shared C++ Library 

在共享c++库中主要的困难是如何对待构造函数和析构函数。 
在SunOS下,构造和使用一个共享的ELF C库是容易的,但是在SunOS下不能 
构造共享的C++库,因为构造函数和析构函数有特别的需求。为止,在ELF 
中的.init和.init section提供了完美的解决方法。 

当构造共享C++库时,我们使用crtbegin.o和crtend.o这两个特殊的版本, 
(它们已经是经过-fPIC的)。对于连接器(link editor)来说,构造共享 
的C++库几乎是和一般的可执行文件一样的。全局的构造函数和析构函数 
被.init和.fini section处理(在上面3.1节中已经讨论过)。 

但一个共享库被映射到进程的地址空间时,动态连接器将在传控制权给程序 
之前执行_init函数,并且将为_fini函数安排在共享库不再需要的时候被 
执行。 

连接选项-shared是告诉gcc以正确的顺序放置必要的辅助文件并且告诉它将 
产生一个共享库。-v选项将显示什么文件什么选项被传到了连接器 
(link editor). 

[alert7@redhat62 dl]# gcc -v -shared -o libbar.so libbar.o 
Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/specs 
gcc version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release) 
/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/collect2 -m elf_i386 
-shared -o libbar.so /usr/lib/crti.o /usr/lib/gcc-lib/i386-redhat 
    -linux/egcs-2.91.66/crtbeginS.o 
-L/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66 
-L/usr/i386-redhat-linux/lib libbar.o -lgcc -lc --version-script 
/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/libgcc.map 
-lgcc /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/crtendS.o 
/usr/lib/crtn.o 

crtbeginS.o和crtendS.o用-fPIC编译的两个特殊的版本。带上-shared 
创建共享库是重要的,因为那些辅助的文件也提供其他服务。我们将在 
5.3节中讨论。 

相关阅读 更多 +
排行榜 更多 +
盛世天下

盛世天下

角色扮演 下载
镇魂街破晓手游

镇魂街破晓手游

角色扮演 下载
磨碎物品

磨碎物品

休闲益智 下载