Linux 动态链接 全攻略

2 分钟读完

马上就要五一了,相信很多人都在查各种旅游攻略,有了攻略,你可以找一条最优路线,用最短的时间,浏览最美的景点,品尝最好的美食。 同样,本攻略的目的就是快速全面的掌握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的过程可以分为两步:

  1. 编译成位置无关的代码(PIC: Position-Independent Code)

    g++ -c -fpic hello.*

  2. 链接生成共享库文件

    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点,分析多种不同的实现方法:

  1. 编译时包含头文件
    • 直接将生成so的代码头文件拷贝到使用地点,如(cp libhello/hello.h test_libhello/)
    • 将生成so的代码头文件拷贝到系统指定的include目录,如(sudo cp libhello/hello.h /usr/include/)
    • 使用-I参数指定so的代码头文件目录,如(g++ -I../libhello -c test.cc)
  2. 链接时 链接时和运行时都需要so安装,如果在链接时还没有安装so的话,可使用-L参数指定so所在目录,如

     g++ test.o -L../libhello -lhello -o test
    
  3. 运行时 如果没有安装so,在链接时使用了-L参数,这时就会找不到so文件,导致上面第三种错误。 gcc有一个参数rpath(即run path)可以在链接时指定运行路径,让程序在运行时找到so, 如

     g++ test.o -L../libhello -Wl,-rpath=../libhello -lhello -o test
    

    上面除了-rpath选项,还有一个-Wl,它的作用是将逗号后面的选项传递给链接器ld

  4. 安装so 由于链接和运行时都需要安装so,这里单独说一下安装的不同方式
    1. 通过环境变量LD_PRELOAD,运行命令LD_PRELOAD=../libhello ./test
    2. 通过环境变量LD_LIBRARY_PATH,修改~/.bashrc,在尾部加入export LD_LIBRARY_PATH=/home/username/libhello:$LD_LIBRARY_PATH
    3. ld.so.conf
 cat /etc/ld.so.conf 
 	include /etc/ld.so.conf.d/*.conf
 ls /etc/ld.so.conf.d/
      	i386-linux-gnu_EGL.conf  i386-linux-gnu_GL.conf  i686-linux-gnu.conf  libc.conf  vmware-tools-libraries.conf
 cat /etc/ld.so.conf.d/libc.conf
  	# libc default configuration
      	/usr/local/lib
	通过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
 sudo vim /etc/ld.so.conf.d/hello.conf
 ldconfig -p |grep hello
 sudo ldconfig
 ldconfig -p |grep hello
 	libhello.so (libc6) => /home/onestraw/code/so/libhello/libhello.so
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 ./test

5. 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. 相关资料


  1. gcc dynamic library 详解
  2. Linux Commands For Shared Library Management & Debugging Problem
  3. Linux共享库路径配置及Ldconfig
  4. linux 头文件以及库的路径
  5. 书籍《程序员的自我修养——链接、装载与库》7,8章
  6. 利用LD_PRELOAD进行hook
  7. 动态加载so库
  8. C++使用dlsym的问题及解决
  9. 创建静态连接库ar
  10. windows dll技术

end by geeksword
from 一根稻草

留下评论