Linux 动态链接 全攻略
马上就要五一了,相信很多人都在查各种旅游攻略,有了攻略,你可以找一条最优路线,用最短的时间,浏览最美的景点,品尝最好的美食。 同样,本攻略的目的就是快速全面的掌握Linux下动态链接的相关知识,包括:
- 创建so(共享库)
- 使用so
- inject so
- so的链接查找顺序
- 动态加载so
- so的相关命令(hack so)
- 学习资料
1. 实例
talk is cheap, show me the code
源码下载
目录结构:
.
├── inject_libhello //伪造的libhello.so
│ ├── build.sh
│ ├── fake_hello.cc
│ ├── fake_hello.h
│ └── libfakehello.so
├── libhello //正确的libhello.so
│ ├── build.sh
│ ├── hello.cc
│ ├── hello.h
│ └── libhello.so
├── test_dlfcn //测试dlopen, dlsym
│ ├── build.sh
│ ├── test
│ └── test.cc
└── test_libhello //so测试程序
├── build.sh
├── test
├── test.cc
├── test_inject.sh
└── test.o
2. 创建共享库so
Windows平台下动态链接库dll(dynamic linking library), Linux平台下叫做共享库so(shared object).
创建so不像创建dll那么复杂,创建dll需要指定导出符号,使用时还要指定导入符号,
创建so没有这些限制,正常写代码就OK了,只需要在编译链接时加入一些额外操作。
cd libhello
cat build.sh
#!/bin/sh
g++ -fPIC hello.* -shared -o libhello.so
echo "[+] build done!"
创建so的过程可以分为两步:
-
编译成位置无关的代码(PIC: Position-Independent Code)
g++ -c -fpic hello.*
-
链接生成共享库文件
g++ -shared -o libhello.so hello.o
3. 使用so
对于一个libnameofso.so的库,链接方式是 -lnameofso,像这种简单问题不再赘述。
先来看一下Linux平台下使用动态共享库最常见的问题,主要分为3种(生成可执行程序阶段有2种,运行时1种)
- 编译时找不到头文件,如
test.cc:1:18: fatal error: hello.h: No such file or directory
- 链接时找不到so,如
test.cc:(.text+0x97): undefined reference to 'desc()'
和/usr/bin/ld: cannot find -lhello
- 运行时找不到so,如
./test: error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory
之所以把编译和链接过程分开,就是要把问题定位到准确位置。
我相信所有的初涉Linux环境编程的朋友都遇到过这些问题,其实这3个问题正好对应使用so的3个关键点,
下面针对这3点,分析多种不同的实现方法:
- 编译时包含头文件
- 直接将生成so的代码头文件拷贝到使用地点,如(cp libhello/hello.h test_libhello/)
- 将生成so的代码头文件拷贝到系统指定的include目录,如(sudo cp libhello/hello.h /usr/include/)
- 使用-I参数指定so的代码头文件目录,如(g++ -I../libhello -c test.cc)
-
链接时 链接时和运行时都需要so安装,如果在链接时还没有安装so的话,可使用-L参数指定so所在目录,如
g++ test.o -L../libhello -lhello -o test
-
运行时 如果没有安装so,在链接时使用了-L参数,这时就会找不到so文件,导致上面第三种错误。 gcc有一个参数rpath(即run path)可以在链接时指定运行路径,让程序在运行时找到so, 如
g++ test.o -L../libhello -Wl,-rpath=../libhello -lhello -o test
上面除了-rpath选项,还有一个-Wl,它的作用是将逗号后面的选项传递给链接器ld
- 安装so
由于链接和运行时都需要安装so,这里单独说一下安装的不同方式
- 通过环境变量LD_PRELOAD,运行命令LD_PRELOAD=../libhello ./test
- 通过环境变量LD_LIBRARY_PATH,修改~/.bashrc,在尾部加入export LD_LIBRARY_PATH=/home/username/libhello:$LD_LIBRARY_PATH
- ld.so.conf
通过ld.so.conf,有很多种不同的方法来配置new lib,比如直接拷贝so到/usr/local/lib, 或者将so绝对路径包含到/etc/ld.so.conf中(更优雅的方式是在/etc/ld.so.conf.d/目录下创建新conf文件)
最后不要忘记执行`sudo ldconfig`,否则在ld.so.cache中找不要new lib
4. 系统库/lib和/usr/lib/
将so文件拷贝到这两个目录下不需要执行ldconfig命令就能生效。即使不加载进ld.so.cache文件(通过ldconfig -p查看),也立即生效,执行完sudo ldconfig就加载进cache中了。
该方式不建议。
4. inject so
通过LD_PRELOAD变量完成,libfakehello.so中的全局符号会覆盖libhello.so中的全局符号。
cat test_inject.sh #!/bin/sh LD_PRELOAD=../inject_libhello/libfakehello.so ./test5. so的链接查找顺序
- LD_PRELOAD
- LD_LIBRARY_PATH
- /etc/ld.so.conf (实际是搜索/etc/ld.so.cache)
- /lib/
- /usr/lib/
6. 动态加载so
Dynamically loaded (DL) libraries are libraries that are loaded at times other than during the startup of a program.
cd test_dlfcn
./build.sh
7. so的相关命令(hack so)
命令列表
命令 | 简介 |
---|---|
pkg-config | Return metainformation about installed libraries |
ld | The GNU linker |
ldconfig | configure dynamic linker run-time bindings |
ltrace | A library call tracer |
ldd | print shared library dependencies |
nm | list symbols from object files |
objdump | display information from object files. |
readelf | Displays information about ELF files. |
举例
##pkg-config查找lib和include
$pkg-config --libs libpng
-lpng12
$pkg-config --cflags libpng
-I/usr/include/libpng12
###
##ldconfig刷新ld.so.cache,查找一个so
$ldconfig -p |grep png
libpng12.so.0 (libc6) => /lib/i386-linux-gnu/libpng12.so.0
libpng12.so.0 (libc6) => /usr/lib/i386-linux-gnu/libpng12.so.0
libpng12.so (libc6) => /usr/lib/i386-linux-gnu/libpng12.so
###
##ltrace跟踪so
$ltrace -l ../libhello/libhello.so ./test
_Z5helloSs(0xbfc13404, 0xbfc13400, 0xbfc1340f, 0x8048af2, 0x8536014) = 0xbfc13404
Hello onestraw !
_Z4descv(0xbfc13408, 0x8048820, 0xbfc1340f, 0x8048af2, 0x8536014) = 0xbfc13408
Desc: hello library
Author: geeksword
+++ exited (status 0) +++
###
##ldd枚举依赖库
$ldd test
linux-gate.so.1 => (0xb77c8000)
libhello.so => ../libhello/libhello.so (0xb77c3000)
libstdc++.so.6 => /usr/lib/i386-linux-gnu/libstdc++.so.6 (0xb76cc000)
libgcc_s.so.1 => /lib/i386-linux-gnu/libgcc_s.so.1 (0xb76ad000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7503000)
libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xb74d7000)
/lib/ld-linux.so.2 (0xb77c9000)
###
##nm查询符号表
$nm -D libhello.so
....
00000a0b T desc
0000098c T hello
###
##objdump查询符号表
$objdump -T libhello.so
....
0000098c g DF .text 0000007f Base hello
00000a0b g DF .text 00000068 Base desc
###
##readelf读取符号表
$readelf -s libhello.so
...
66: 0000098c 127 FUNC GLOBAL DEFAULT 11 hello
...
77: 00000a0b 104 FUNC GLOBAL DEFAULT 11 desc
8. 相关资料
- gcc dynamic library 详解
- Linux Commands For Shared Library Management & Debugging Problem
- Linux共享库路径配置及Ldconfig
- linux 头文件以及库的路径
- 书籍《程序员的自我修养——链接、装载与库》7,8章
- 利用LD_PRELOAD进行hook
- 动态加载so库
- C++使用dlsym的问题及解决
- 创建静态连接库ar
- windows dll技术
end by geeksword
from 一根稻草
留下评论