Skip to content

Latest commit

 

History

History
487 lines (393 loc) · 19.1 KB

20231207_01.md

File metadata and controls

487 lines (393 loc) · 19.1 KB

转载 - Linux 环境变量之 LD_PRELOAD & LD_LIBRARY_PATH & LD_DEBUG & rpath

作者

digoal

日期

2023-12-07

标签

PostgreSQL , PolarDB , DuckDB , Linux , 链接库 , 调试 , LD_PRELOAD , LD_LIBRARY_PATH , LD_DEBUG , rpath


背景

原文链接

https://blog.csdn.net/llm_hao/article/details/115493516

目录

环境变量

1、LD_LIBRARY_PATH

2、LD_PRELOAD

3、LD_DEBUG

4、rpath 路径

5、小结

文中源码

Linux环境

showBytes.h

showBytes.c

envTest.c

GCC命令

参考

环境变量

Linux 系统提供了多种方法来改变动态库连接器装载共享库路径的方法。通过使用此类方法,我们可以实现一些特殊的需求,如:动态库的调试、改变应用程序的行为方式等。

Linux 查看环境变量的 2 种方式:

$ env               #environment的简写  
$ export  

下面主要描述 4 种常用的修改程序运行时环境变量的方式:

1、LD_LIBRARY_PATH

LD_LIBRARY_PATH 可以临时改变应用程序的共享库(如:动态库)查找路径,而不会影响到系统中的其他程序。

$ echo ${LD_LIBRARY_PATH}  

在 Linux 系统中,LD_LIBRARY_PATH 是一个由若干个路径组成的环境变量,每个路径之间由冒号隔开。默认情况下 LD_LIBRARY_PATH 为空。如果我们为某个进程设置了 LD_LIBRARY_PATH,那么进程启动时,动态链接器会优先查找 LD_LIBRARY_PATH 指定的目录。

动态链接器转载或是查找共享库(如:动态库、静态库)的顺序为:

  • 环境变量 LD_LIBRARY_PATH 指定的路径;
  • 路径缓存文件 /etc/ld.so.cache 指定的路径;
  • 默认共享库目录,先 /usr/lib ,然后 /lib 。

举例:

$ gcc -I/home/myTest/envTest -o envTest envTest.c -L/home/myTest/envTest -lshowBytes  
$ export LD_LIBRARY_PATH=/home/myTest/envTest  
$ ./envTest  

此时,我们检查下 envTest 的动态库依赖关系如下:

ldd envTest  
        linux-vdso.so.1 (0x00007ffef30dd000)  
        libshowBytes.so => /home/myTest/envTest/libshowBytes.so (0x00007f977735a000)  
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9777161000)  
        /lib64/ld-linux-x86-64.so.2 (0x00007f9777366000)  

Linux 中还有另外一种方法可以实现与 LD_LIBRARY_PATH 类似的功能,即直接运行动态链接器来启动程序,如:

# 示例中是 x86_64 环境  
$ /lib64/ld-linux-x86-64.so.2 --library-path /home/myTest/envTest/ /home/myTest/envTest/envTest  

或者编译的时候,采用下面的命令

gcc -I/home/myTest/envTest -o envTest envTest.c ./libshowBytes.so  

这样运行也是没有问题的。同时,我们重新检查下 envTest 的动态库依赖关系如下:

ldd envTest  
        linux-vdso.so.1 (0x00007fff299ba000)  
        ./libshowBytes.so (0x00007f08e37e0000)  
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f08e35e7000)  
        /lib64/ld-linux-x86-64.so.2 (0x00007f08e37ec000)  

注意:

  • 尽管 LD_LIBRARY_PATH 对于共享库的开发和测试来说十分方便,但是不应该被滥用。随意修改 LD_LIBRARY_PATH 并且将其导出至全局范围,将可能引起其他应用程序运行出现问题。
  • 此外, LD_LIBRARY_PATH 也会影响GCC 编译时查找库的路径,它里面包含的路径相当于链接时GCC的 "-L" 参数。

同样,为了避免对其他运行程序和后续测试产生影响,需要及时取消该环境变量设置:

unset LD_LIBRARY_PATH  

2、LD_PRELOAD

在 LD_PRELOAD 中指定的文件会在动态链接器按照固定规则搜索共享库之前装载,他比 LD_LIBRARY_PATH 所指定的目录中的共享库还要优先。无论时否依赖它们,LD_PRELOAD 中指定的共享库或目标文件都会被装载。

此外,由于全局符号介入机制的存在,LD_PRELOAD 中指定的共享库或目标文件中的全局符号就会覆盖后面加载的同名全局符号,这使得我们可以很方便的做到改写标准 C 库中的某个或某几个函数而不影响其他函数,对于程序的调试或测试非常有用。

用来做测试非常方便、简单:

export LD_PRELOAD=./libshowBytes.so  
./envTest  

此时,检查下动态了依赖关系如下:

ldd envTest  
        linux-vdso.so.1 (0x00007ffc6efad000)  
        ./libshowBytes.so (0x00007fcee5ab8000)  
        libshowBytes.so => not found  
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fcee58bf000)  
        /lib64/ld-linux-x86-64.so.2 (0x00007fcee5ac4000)  

其中 ./libshowBytes.so (0x00007fcee5ab8000) 就是刚刚添加的共享库。

但是另外我们也发现了,尽快我们已经添加了一个./libshowBytes.so 共享库(其位置是位于libshowBytes.so 之前),但是另外的一个 libshowBytes.so 还是显示的 => not found

请仔细观察,并比较 LD_PRELOAD 环境变量与 LD_LIBRARY_PATH 的不同。

注意:

  • 使用 LD_PRELOAD 环境变量最好仅仅只是用于测试,因为会影响到全局符号;
  • LD_PRELOAD 环境变量优先级要比 LD_LIBRARY_PATH 更高;
  • LD_PRELOAD 环境变量与LD_LIBRARY_PATH 环境变量的使用是不同的。
  • 正常程序的使用,应该尽量避免使用 LD_PRELOAD ,特别是发布版本的程序运行不应该依赖 LD_PRELOAD

同样,为了避免对其他运行程序和后续测试产生影响,需要及时取消该环境变量设置:

unset LD_PREALOD  

3、LD_DEBUG

该环境变量可以打开动态链接器的调试功能,当我们设置该变量时,动态链接器会在运行时打印出各种有用的信息,对于我们开发和调试共享库有很大的帮助。

LD_DEBUG 可以设置的值有:

  • “files”,显示整个装载过程;
  • “libs”,显示共享库查找过程;
  • “symbols”,显示符号的查找过程;
  • “bindings”,显示动态链接的符号绑定过程;
  • “versions”,显示符号的版本依赖关系;
  • “reloc”,显示重定位信息;

例如,查看整个装载过程:

LD_DEBUG=files ./envTest  
     18721:  
     18721:     file=./libshowBytes.so [0];  needed by ./envTest [0]  
     18721:     file=./libshowBytes.so [0];  generating link map  
     18721:       dynamic: 0x00007f6e2c8ede20  base: 0x00007f6e2c8ea000   size: 0x0000000000004040  
     18721:         entry: 0x00007f6e2c8eb0a0  phdr: 0x00007f6e2c8ea040  phnum:                 11  
     18721:  
     18721:  
     18721:     file=libc.so.6 [0];  needed by ./envTest [0]  
     18721:     file=libc.so.6 [0];  generating link map  
     18721:       dynamic: 0x00007f6e2c8dcb80  base: 0x00007f6e2c6f1000   size: 0x00000000001f1660  
     18721:         entry: 0x00007f6e2c7151f0  phdr: 0x00007f6e2c6f1040  phnum:                 14  
     18721:  
     18721:  
     18721:     calling init: /lib/x86_64-linux-gnu/libc.so.6  
     18721:  
     18721:  
     18721:     calling init: ./libshowBytes.so  
     18721:  
     18721:  
     18721:     initialize program: ./envTest  
     18721:  
     18721:  
     18721:     transferring control: ./envTest  
     18721:  
calling show_twocomp  
 39 30  
 c7 cf  
     18721:  
     18721:     calling fini: ./envTest [0]  
     18721:  
     18721:  
     18721:     calling fini: ./libshowBytes.so [0]  
     18721:  

或者查看依赖共享库的查找过程:

 LD_DEBUG=libs ./envTest  
     18493:     find library=libc.so.6 [0]; searching  
     18493:      search path=/home/myTest/envTest/tls/haswell/x86_64:/home/myTest/envTest/tls/haswell:/home/myTest/envTest/tls/x86_64:/home/myTest/envTest/tls:/home/myTest/envTest/haswell/x86_64:/home/myTest/envTest/haswell:/home/myTest/envTest/x86_64:/home/myTest/envTest            (LD_LIBRARY_PATH)  
     18493:       trying file=/home/myTest/envTest/tls/haswell/x86_64/libc.so.6  
     18493:       trying file=/home/myTest/envTest/tls/haswell/libc.so.6  
     18493:       trying file=/home/myTest/envTest/tls/x86_64/libc.so.6  
     18493:       trying file=/home/myTest/envTest/tls/libc.so.6  
     18493:       trying file=/home/myTest/envTest/haswell/x86_64/libc.so.6  
     18493:       trying file=/home/myTest/envTest/haswell/libc.so.6  
     18493:       trying file=/home/myTest/envTest/x86_64/libc.so.6  
     18493:       trying file=/home/myTest/envTest/libc.so.6  
     18493:      search cache=/etc/ld.so.cache  
     18493:       trying file=/lib/x86_64-linux-gnu/libc.so.6  
     18493:  
     18493:  
     18493:     calling init: /lib/x86_64-linux-gnu/libc.so.6  
     18493:  
     18493:  
     18493:     calling init: ./libshowBytes.so  
     18493:  
     18493:  
     18493:     initialize program: ./envTest  
     18493:  
     18493:  
     18493:     transferring control: ./envTest  
     18493:  
calling show_twocomp  
 39 30  
 c7 cf  
     18493:  
     18493:     calling fini: ./envTest [0]  
     18493:  
     18493:  
     18493:     calling fini: ./libshowBytes.so [0]  
     18493:  

另外还可以显示符号的查找过程:

LD_DEBUG=symbols ./envTest  
     18868:     symbol=__vdso_clock_gettime;  lookup in file=linux-vdso.so.1 [0]  
     18868:     symbol=__vdso_gettimeofday;  lookup in file=linux-vdso.so.1 [0]  
     18868:     symbol=__vdso_time;  lookup in file=linux-vdso.so.1 [0]  
     18868:     symbol=__vdso_getcpu;  lookup in file=linux-vdso.so.1 [0]  
     18868:     symbol=__vdso_clock_getres;  lookup in file=linux-vdso.so.1 [0]  
     18868:     symbol=_res;  lookup in file=./envTest [0]  
     18868:     symbol=_res;  lookup in file=./libshowBytes.so [0]  
     ........................   
     18868:     symbol=_dl_signal_error;  lookup in file=./envTest [0]  
     18868:     symbol=_dl_signal_error;  lookup in file=./libshowBytes.so [0]  
     18868:     symbol=_dl_signal_error;  lookup in file=/lib/x86_64-linux-gnu/libc.so.6 [0]  
     18868:     symbol=_dl_catch_error;  lookup in file=./envTest [0]  
     18868:     symbol=_dl_catch_error;  lookup in file=./libshowBytes.so [0]  
     18868:     symbol=_dl_catch_error;  lookup in file=/lib/x86_64-linux-gnu/libc.so.6 [0]  
     18868:  
     18868:     calling init: /lib/x86_64-linux-gnu/libc.so.6  
     18868:  
     18868:  
     18868:     calling init: ./libshowBytes.so  
     18868:  
     18868:  
     18868:     initialize program: ./envTest  
     18868:  
     18868:  
     18868:     transferring control: ./envTest  
     18868:  
     18868:     symbol=_dl_find_dso_for_object;  lookup in file=./envTest [0]  
     18868:     symbol=_dl_find_dso_for_object;  lookup in file=./libshowBytes.so [0]  
     18868:     symbol=_dl_find_dso_for_object;  lookup in file=/lib/x86_64-linux-gnu/libc.so.6 [0]  
     18868:     symbol=_dl_find_dso_for_object;  lookup in file=/lib64/ld-linux-x86-64.so.2 [0]  
     18868:     symbol=__tunable_get_val;  lookup in file=./envTest [0]  
     18868:     symbol=__tunable_get_val;  lookup in file=./libshowBytes.so [0]  
     18868:     symbol=__tunable_get_val;  lookup in file=/lib/x86_64-linux-gnu/libc.so.6 [0]  
     18868:     symbol=__tunable_get_val;  lookup in file=/lib64/ld-linux-x86-64.so.2 [0]  
calling show_twocomp  
     18868:     symbol=printf;  lookup in file=./envTest [0]  
     18868:     symbol=printf;  lookup in file=./libshowBytes.so [0]  
     18868:     symbol=printf;  lookup in file=/lib/x86_64-linux-gnu/libc.so.6 [0]  
     18868:     symbol=putchar;  lookup in file=./envTest [0]  
     18868:     symbol=putchar;  lookup in file=./libshowBytes.so [0]  
     18868:     symbol=putchar;  lookup in file=/lib/x86_64-linux-gnu/libc.so.6 [0]  
 39 30  
 c7 cf  
     18868:  
     18868:     calling fini: ./envTest [0]  
     18868:  
     18868:  
     18868:     calling fini: ./libshowBytes.so [0]  
     18868:  

4、rpath 路径

rpath 路径需要在编译时指定,因为这些信息会被写入到了 ELF 文件中。

gcc showBytes.c -fPIC -shared -o libshowBytes.so -I. -L. -lshowBytes -g -Wall -Wl,-rpath-link $(pwd)  
gcc -o envTest envTest.c -I. -L . -lshowBytes -g -Wall -Wl,-rpath $(pwd)  

此时,检查下动态了依赖关系如下:

ldd envTest  
        linux-vdso.so.1 (0x00007fffe4fa3000)  
        libshowBytes.so => /home/myTest/envTest/libshowBytes.so (0x00007fe8017ae000)  
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe8015b5000)  
        /lib64/ld-linux-x86-64.so.2 (0x00007fe8017ba000)  

可以看出共享库(如:动态库)是可以直接找到的。

5、小结

基于以上描述,我们可以总结出共享库(如:动态库)搜索顺序如下:

  • LD_PRELOAD 环境变量指定的共享库路径;
  • LD_LIBRARY_PATH 环境变量指定的共享库路径;
  • -rpath 链接时指定的共享库路径;
  • /etc/ld.so.conf 配置文件指定的共享库路径;
  • 默认共享库路径,/usr/liblib
  • 此外,LD_PRELOAD 环境变量与 LD_LIBRARY_PATH 环境变量是不同的。

疑问:以上这些查找路径如何验证他们的优先级呢?

答疑:

  • 比较简单的做法就是在这几个位置分别放置同名,但是不同作用的库。通过程序运行结果来看看到底优先使用哪个路径下的库。

文中源码

Linux环境

$ uname -a  
Linux lm 5.4.0-100-generic #113-Ubuntu SMP Thu Feb 3 18:43:29 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux  
  
$ gcc -v  
Using built-in specs.  
COLLECT_GCC=gcc  
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper  
OFFLOAD_TARGET_NAMES=nvptx-none:hsa  
OFFLOAD_TARGET_DEFAULT=1  
Target: x86_64-linux-gnu  
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.4.0-1ubuntu1~20.04.1' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-9-Av3uEd/gcc-9-9.4.0/debian/tmp-nvptx/usr,hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu  
Thread model: posix  
gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1)  

所有代码存放路径为: /home/myTest/envTest

$ tree  
.  
├── envTest  
├── envTest.c  
├── libshowBytes.so  
├── showBytes.c  
└── showBytes.h  

showBytes.h

/* $begin show-bytes */  
#ifndef _SHOW_BYTES_H_  
#define _SHOW_BYTES_H_  
  
#include <stdio.h>  
  
typedef unsigned char *byte_pointer;  
  
void show_bytes(byte_pointer start, size_t len);  
  
void show_int(int x);  
  
void show_float(float x);  
  
void show_pointer(void *x);   
  
/* $end show-bytes */  
#endif  

showBytes.c

/* $begin show-bytes */  
#include <showBytes.h>  
  
void show_bytes(byte_pointer start, size_t len) {  
    size_t i;  
    for (i = 0; i < len; i++)  
	printf(" %.2x", start[i]);      
    printf("\n");  
}  
  
void show_int(int x) {  
    show_bytes((byte_pointer) &x, sizeof(int));   
}  
  
void show_float(float x) {  
    show_bytes((byte_pointer) &x, sizeof(float));  
}  
  
void show_pointer(void *x) {  
    show_bytes((byte_pointer) &x, sizeof(void *));  
}  
/* $end show-bytes */  

envTest.c

/* $begin environment Test */  
#include <showBytes.h>  
#include <stdlib.h>  
  
void test_show_bytes(int val) {  
    int   ival = val;  
    float fval = (float) ival;  
    int   *pval = &ival;  
  
    show_int(ival);  
    show_float(fval);  
    show_pointer(pval);  
}  
  
void show_twocomp()   
{  
    short x = 12345;   
    short mx = -x;   
      
    show_bytes((byte_pointer) &x, sizeof(short));   
    show_bytes((byte_pointer) &mx, sizeof(short));   
}  
  
int main(int argc, char *argv[])  
{  
    int l_val = 12345;  
  
    if (argc > 1) {  
		if (argc > 1) {  
			l_val = strtol(argv[1], NULL, 0);  
		}  
		printf("calling test_show_bytes\n");  
		test_show_bytes(l_val);  
    } else {  
		printf("calling show_twocomp\n");  
		show_twocomp();  
    }  
    return 0;  
}  
/* $end environment Test */  

GCC命令

需要注意的是,动态共享库的名字必须是 libXXXX.so 开头的。本篇中例子是 libshowBytes.so

gcc -g -Wall -I/home/myTest/envTest -fPIC -shared -o libshowBytes.so showBytes.c  
gcc -g -Wall -I/home/myTest/envTest -o envTest envTest.c -L/home/myTest/envTest -lshowBytes  
  
gcc -g -Wall -I/home/myTest/envTest -o envTest envTest.c ./showBytes.so  

其中最后的两条命令是等价的。即可以通过 -L 指定路径,或者是直接将所需的 so 库加载进去(直接运行程序即可,无需再配置环境变量)。

参考

版权声明:本文为CSDN博主「lm_hao」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/llm_hao/article/details/115493516

digoal's wechat