【程序狂魔】掌握LD_PRELOAD轻松进行程序修改和优化的绝佳方法!

一、简介

LD_PRELOAD是Linux/Unix系统的一个环境变量,它可以影响程序的运行时的链接,它允许在程序运行前定义优先加载的动态链接库。通过这个环境变量,可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖系统的函数库。

LD_PRELOAD 超脱于动态链接库的搜索路径先后顺序之外,它可以指定在程序运行前优先加载的动态链接库。

1.1、LD_PRELOAD的概念和作用

LD_PRELOAD是Linux系统中的一个环境变量,用于在程序运行时动态加载指定的共享库。LD_PRELOAD的作用是在程序运行前,将指定的共享库加载到程序的内存中。这样,程序在运行时会优先使用该共享库中的符号,而不是系统默认的符号。LD_PRELOAD可以用于替换程序本身的函数,增加程序的功能或者调试程序。

LD_PRELOAD的工作原理是,当程序需要调用某个符号时,系统会先在程序自身的符号表中查找,如果找不到,则会在LD_PRELOAD指定的共享库中查找。如果在LD_PRELOAD指定的共享库中找到了该符号,则使用该符号中的代码。

使用LD_PRELOAD可以实现一些特殊的功能,例如:

  1. 动态库劫持:可以用LD_PRELOAD来劫持程序中的函数,替换为自己编写的函数,实现一些特殊的功能。

  2. 程序调试:可以用LD_PRELOAD来替换程序中的函数,增加一些调试信息,例如,在程序中调用printf函数时,可以用LD_PRELOAD来替换为自己编写的函数,输出调试信息。

  3. 库版本控制:可以用LD_PRELOAD来强制程序使用指定版本的共享库,以避免程序在不同版本的环境中产生兼容性问题。

需要注意的是,使用LD_PRELOAD需要注意一些安全和兼容性问题。为了避免程序崩溃或产生意外的行为,替换的函数必须与被替换的函数具有相同的函数原型和行为。在使用LD_PRELOAD时需要注意共享库与程序之间的交互,避免产生意外的结果。

1.2、LD_PRELOAD的使用场景和优势

LD_PRELOAD的使用场景非常广泛,主要包括以下几个方面:

  1. 动态库劫持
    使用LD_PRELOAD可以劫持程序中的函数,替换为自己编写的函数,实现一些特殊的功能。例如,可以用LD_PRELOAD来Hook系统调用,实现一些系统级别的监控或控制。

  2. 程序调试
    使用LD_PRELOAD可以替换程序中的函数,增加一些调试信息,例如,在程序中调用printf函数时,可以用LD_PRELOAD来替换为自己编写的函数,输出调试信息。

  3. 库版本控制
    使用LD_PRELOAD可以强制程序使用指定版本的共享库,以避免程序在不同版本的环境中产生兼容性问题。

  4. 代码注入
    使用LD_PRELOAD可以向程序中注入一些代码,实现一些与程序无关的功能,例如统计程序使用情况、监控程序运行状态等。

LD_PRELOAD的优势主要包括以下几个方面:

  1. 灵活性
    使用LD_PRELOAD可以在运行时动态加载指定的共享库,灵活性非常高,可以根据需要替换程序中的任何函数。

  2. 易用性
    使用LD_PRELOAD非常简单,只需要设置LD_PRELOAD环境变量即可,不需要修改程序本身的代码。

  3. 可移植性
    使用LD_PRELOAD不依赖于具体的编程语言和操作系统,可以在不同的平台上使用。

  4. 高效性
    使用LD_PRELOAD可以减少代码修改和重编译的时间,提高程序的开发效率。

二、动态库劫持

2.1、动态库劫持的概念和实现原理

动态库劫持是一种利用共享库的特性,在程序运行时动态替换程序中的函数的技术。动态库劫持可以用于Hook系统调用、修改程序行为、监控程序运行状态等方面。下面介绍动态库劫持的概念和实现原理。

概念:
动态库劫持是指通过替换程序中的动态链接库(共享库)中的函数,改变程序的行为的一种技术。在程序启动时,操作系统会在预定义的路径中查找所需要的共享库,加载到内存中,并建立程序和共享库之间的链接关系。在程序运行时,如果程序需要用到共享库中的某个函数,就会通过链接关系在共享库中查找该函数,并调用它。动态库劫持的技术就是通过替换共享库中的函数,来改变程序的行为。

实现原理:
动态库劫持的实现原理是利用了Linux系统中的一个环境变量LD_PRELOAD。LD_PRELOAD是一个环境变量,可以用于在程序运行时动态加载指定的共享库。当程序需要调用共享库中的某个函数时,系统会先在程序自身的符号表中查找,如果找不到,则会在LD_PRELOAD指定的共享库中查找。如果在LD_PRELOAD指定的共享库中找到了该函数,则使用该函数中的代码。

动态库劫持的实现步骤如下:

  1. 编写共享库中的替代函数,并编译成共享库。
  2. 设置LD_PRELOAD环境变量,指定要加载的共享库。
  3. 运行需要Hook的程序。

当程序需要调用被替换的函数时,会先在程序自身的符号表中查找,如果找不到,则会在LD_PRELOAD指定的共享库中查找。如果在LD_PRELOAD指定的共享库中找到了该函数,则使用该函数中的代码,从而实现了动态库劫持。

2.2、使用LD_PRELOAD动态库劫持调试程序

  1. 编写共享库。
    首先,需要编写一个共享库,该共享库中包含要劫持的函数的替代函数。例如,如果要劫持系统中的open函数,可以编写一个名为myopen的函数,并将其编译成共享库。

  2. 设置LD_PRELOAD环境变量。
    在命令行中设置LD_PRELOAD环境变量,指定要加载的共享库。例如,要劫持某个程序,可以在命令行中输入以下命令:

$ LD_PRELOAD=./libmy.so ./myprogram

其中,./libmy.so是编译好的共享库的路径,./myprogram是要运行的程序的路径。

  1. 运行程序。
    运行要劫持的程序,程序会自动加载指定的共享库,并在运行时使用共享库中的替代函数,从而实现动态库劫持的功能。

注意:替代函数必须与被替换的函数具有相同的函数原型和行为,否则程序可能会崩溃或产生意外的行为。在使用LD_PRELOAD时需要注意共享库与程序之间的交互,避免产生意外的结果。

使用LD_PRELOAD动态库劫持可以增加程序的功能或者调试程序。需要编写一个共享库,并在命令行中设置LD_PRELOAD环境变量,指定要加载的共享库。在运行要劫持的程序时,程序会自动加载共享库,并使用共享库中的替代函数,从而实现动态库劫持的功能。

三、使用示例

LD_PRELOAD和hook很像。

编写如下代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc,char **argv)
{
        int fd;
        fd=open("/home/fly/workspace/proj/ld_test.c",O_RDONLY);
        if(fd!=-1)
                fprintf(stdout,"openfile successed!\n");
        else
                fprintf(stdout,"openfile failed!\n");
        close(fd);
        return 0;
}

代码很简单,调用open(…)函数打开文件,输出打开成功或打开失败。直接执行就是调用的系统调用:

$ gcc -o ld_test ld_test.c
$ ./ld_test
openfile successed!

open函数不是我们实现的,是系统的api函数,这是系统为应用程序提供的库函数,即posix api。

现在,我们实现一个open函数,内部调用系统调用__open(char *,int,int)。

#include <stdio.h>
  
extern int __open(char *,int,int);

int open(char *path,int flags,int mode)
{
        printf("open : %s\n",path);
        return __open(path,flags,mode);
}

将其编译成动态库:

gcc -shared -fPIC -o my_open.so my_open.c

使用LD_PRELOAD执行:

LD_PRELOAD=./my_open.so ./ld_test

执行结果:

open : /home/fly/workspace/proj/ld_test.c
openfile successed!

可以发现,相对前面的直接执行,输出端多了这么一句 ”open : /home/fly/workspace/proj/ld_test.c“ ;这说明调用了我们实现的open()函数。

总结

  1. 我们可以自己实现一个函数,底层调用系统调用,理由LD_PRELOAD优先加载我们的函数。这可以有一个应用,就是可以检测内存泄漏,我们实现一个malloc函数,使用的还是系统调用,但是可以在多做一些内存泄漏的检测业务逻辑;和hook一样。

  2. DPDK高性能处理框架VPP的vcl就是利用LD_PRELOAD来加载应用,vcl内部实现了相关的posix api函数。要注意的是,vcl和应用不是运行在同一个进程;VPP的协议栈处理完数据后,是将数据放入一段共享内存中,vcl库从共享内存取出数据给到应用。

  3. VPP的开发模式:VPP+应用程序和VPP Plugin。
    在这里插入图片描述

LD_PRELOAD是Linux系统中的一个环境变量,用于在程序运行时动态加载指定的共享库,可以用于替换程序本身的函数,增加程序的功能或者调试程序。常见的用途包括:

  1. 动态库劫持
    可以用LD_PRELOAD来劫持程序中的函数,替换为自己编写的函数,实现一些特殊的功能。例如,可以用LD_PRELOAD来Hook系统调用,实现一些系统级别的监控或控制。

  2. 程序调试
    可以用LD_PRELOAD来替换程序中的函数,增加一些调试信息,例如,在程序中调用printf函数时,可以用LD_PRELOAD来替换为自己编写的函数,输出调试信息。

  3. 库版本控制
    可以用LD_PRELOAD来强制程序使用指定版本的共享库,以避免程序在不同版本的环境中产生兼容性问题。

使用LD_PRELOAD需要注意以下几点:

  1. 共享库必须是可执行文件,即必须具有x权限。

  2. 共享库必须是使用-fPIC编译的,以避免地址重定位问题。

  3. 为了避免程序崩溃或产生意外的行为,替换的函数必须与被替换的函数具有相同的函数原型和行为。

  4. 在使用LD_PRELOAD时需要注意共享库与程序之间的交互,避免产生意外的结果。

使用LD_PRELOAD可以用以下命令实现:

export LD_PRELOAD=/path/to/mylibrary.so

其中,/path/to/mylibrary.so是要加载的共享库路径。可以在程序运行前使用该命令设置环境变量,或者将该命令添加到程序启动脚本中,以动态加载指定的共享库。

在这里插入图片描述

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lion Long

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值