IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Linux 动态链接 全攻略

    一根稻草发表于 2015-04-25 00:00:00
    love 0

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



沪ICP备19023445号-2号
友情链接