GNU/Linux 库机制笔记
1 Notes
早期发表与 openrays 的个人blog: 2006-09-08 10:44
1. 创建静态库:
gcc -c hello.c -o hello.o ar rcs libhello.a hello.o
2. 使用静态库:
gcc -o test test.c -static -L. -lhello
3. 共享库版本: version.minor.release
4. 构建动态共享库:
gcc/g++下加 -fPIC -shared 参数即可
其中 -fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。可以export LD_DEBUG=files,查看每次加载共享库的实际地址。
其中 -shared 作用于链接阶段,实际传递给链接器ld,让其添加作为共享库所需要的额外描述信息,去除共享库所不需的信息。
可以分解为如下步骤:
I. gcc -c err.c -fPIC -o err.o II. gcc -shared -o liberr.so.0.0 err.o II <==> ld -Bshareable -o liberr.so.0.0 err.o III. ln -s liberr.so.0.0 liberr.so
5. 动态共享库的使用:
a. 由共享库加载器自动加载
gcc -o test test.c -lerr -L. -Wl,-rpath=./ -Wl,option Pass option as an option to the linker. If option contains commas, it is split into multiple options at the commas. -rpath: 指定运行时搜索动态库的路径,可以用环境变量LD_LIBRARY_PATH指定。
b. 程序自己控制加载、符号解析(使用libc6之dl库)
gcc cos.c -o cos -ldl /* cos.c */ #include <stdio.h> #include <dlfcn.h> int main() { void *handle; double (*cosine)(double); char *error; double rev; handle = dlopen("libm.so", RTLD_LAZY); // 加载动态库 if(!handle) { fprintf(stderr, "%s\n", dlerror()); exit(1); } dlerror(); cosine = dlsym(handle, "cos"); // 解析符号cos if((error = dlerror()) != NULL) { fprintf(stderr, "%s\n", error); exit(1); } rev = cosine(3.1415926); // 使用cos函数 printf("The cos result is: %f\n", rev); dlclose(handle); return 0; }
6. GNU/Linux下动态库之加载器为/lib/ld-linux.so, 可执行的。
/lib/ld-linux.so ./cos <===> ./cos
7. 有用的环境变量
LD_LIBRARY_PATH 指定运行时动态库的搜索路径 LD_DEBUG 调试用,其值可为: libs display library search paths reloc display relocation processing files display progress for input file symbols display symbol table processing bindings display information about symbol binding versions display version dependencies all all previous options combined statistics display relocation statistics unused determined unused DSOs help display this help message and exit
8. 搜索含有cos函数的共享库名
nm -AD /lib/* /usr/lib/* 2>/dev/null | grep "cos$"
nm -- 从对象文件中列出符号。
9. 读取ELF文件格式信息
readelf -Ds ./libfoo.so #读出共享库的符号
2 动态链接库机制
以下内容原记载于个人百度空间 http://hi.baidu.com/comcat/item/4166ef00ab975ad11ef04683 时间:2008-07-19 20:47
考虑以下简单的程序:
int main() { printf("f[0] = %d\n", 0x0); printf("f[0] = %d\n", 0x8); return 0; } [root@comcat]$ gdb ./test (gdb) disass main Dump of assembler code for function main: 0x08048374 <main+0>: lea 0x4(%esp),%ecx 0x08048378 <main+4>: and $0xfffffff0,%esp 0x0804837b <main+7>: pushl -0x4(%ecx) 0x0804837e <main+10>: push %ebp 0x0804837f <main+11>: mov %esp,%ebp 0x08048381 <main+13>: push %ecx 0x08048382 <main+14>: sub $0x14,%esp 0x08048385 <main+17>: movl $0x0,0x4(%esp) 0x0804838d <main+25>: movl $0x8048480,(%esp) 0x08048394 <main+32>: call 0x80482d8 <printf@plt> 0x08048399 <main+37>: movl $0x8,0x4(%esp) 0x080483a1 <main+45>: movl $0x8048480,(%esp) 0x080483a8 <main+52>: call 0x80482d8 <printf@plt> 0x080483ad <main+57>: mov $0x0,%eax 0x080483b2 <main+62>: add $0x14,%esp 0x080483b5 <main+65>: pop %ecx 0x080483b6 <main+66>: pop %ebp 0x080483b7 <main+67>: lea -0x4(%ecx),%esp 0x080483ba <main+70>: ret End of assembler dump. (gdb) x /8i 0x80482d8 0x80482d8 <printf@plt>: jmp *0x804958c ----> 0x804958c 处的值为 0x80482de,则其实际上跳到了下一条指令处。 0x80482de <printf@plt+6>: push $0x10 0x80482e3 <printf@plt+11>: jmp 0x80482a8 <_init+48> 0x80482e8: Cannot access memory at address 0x80482e8 (gdb) x /4x 0x804958c 0x804958c <_GLOBAL_OFFSET_TABLE_+20>: 0x080482de 0x00000000 0x00000000 0x0804949c (gdb) b *0x0804838d Breakpoint 1 at 0x804838d: file test.c, line 3. (gdb) r Starting program: /home/comcat/develop/test/dl/test Breakpoint 1, 0x0804838d in main () at test.c:3 3 printf("f[0] = %d\n", 0); (gdb) si 0x08048394 3 printf("f[0] = %d\n", 0); (gdb) 0x080482d8 in printf@plt () (gdb) 0x080482de in printf@plt () (gdb) 0x080482e3 in printf@plt () (gdb) x /4i 0x80482a8 0x80482a8 <_init+48>: pushl 0x804957c 0x80482ae <_init+54>: jmp *0x8049580 0x80482b4 <_init+60>: add %al,(%eax) 0x80482b6 <_init+62>: add %al,(%eax) (gdb) x /4x 0x8049580 0x8049580 <_GLOBAL_OFFSET_TABLE_+8>: 0xb7fc02d0 0x080482be 0xb7e6e370 0x080482de ----> 0xb7fc02d0 处为动态链接器的解析函数 _dl_runtime_resolve() (gdb) si 0x080482a8 in ?? () (gdb) si 0x080482ae in ?? () (gdb) si 0xb7fc02d0 in _dl_runtime_resolve () from /lib/ld-linux.so.2 (gdb) b *0x080483a1 Breakpoint 2 at 0x80483a1: file test.c, line 4. (gdb) c Continuing. f[0] = 0 Breakpoint 2, 0x080483a1 in main () at test.c:4 ----> 第一次 printf 调用已经完,动态链接器已经修改 GOT 的相应项为 printf 的地址 4 printf("f[0] = %d\n", 8); (gdb) x /8i 0x80482d8 ----> PLT 没有变化,但原 GOT 入口 0x804958c 处的值已经被改写为 printf 的地址 0x80482d8 <printf@plt>: jmp *0x804958c 0x80482de <printf@plt+6>: push $0x10 0x80482e3 <printf@plt+11>: jmp 0x80482a8 <_init+48> 0x80482e8: add %al,(%eax) 0x80482ea: add %al,(%eax) 0x80482ec: add %al,(%eax) 0x80482ee: add %al,(%eax) 0x80482f0 <printf@plt+24>: xor %ebp,%ebp (gdb) x /4x 0x804958c 0x804958c <_GLOBAL_OFFSET_TABLE_+20>: 0xb7e9f170 0x00000000 0x00000000 0x0804949c (gdb) x /4i 0xb7e9f170 0xb7e9f170 <printf>: push %ebp 0xb7e9f171 <printf+1>: mov %esp,%ebp 0xb7e9f173 <printf+3>: push %ebx 0xb7e9f174 <printf+4>: call 0xb7e6e28f <__i686.get_pc_thunk.bx>
x86 下动态链接机制,动态链接器不会更改 PLT,只会在用到相应链接函数时,才会去解析符号,然后将对应的地址更新到 GOT 的相应项,GOT 里亦没有代码,不需要执行。