ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

LD_PRELOAD不会影响带有RTLD_NOW的dlopen()

2019-05-16 13:38:41  阅读:566  来源: 互联网

标签:c-3 linux shared-libraries dlopen ld-preload


如果我直接使用共享库中的函数,即在我的代码中声明它并在编译期间链接,LD_PRELOAD工作正常.但是如果我使用dlopen()/ dlsym()代替LD_PRELOAD没有效果!

问题是我想调试一个使用dlopen()加载一些插件的程序,并且它使用绝对文件名,所以简单地使用LD_LIBRARY_PATH将不起作用.

这是一个说明问题的示例代码.

./libfoo.so

void foo() {
    printf("version 1\n");
}

./preload/libfoo.so

void foo() {
    printf("version 2\n");
}

main.c中

#include <stdio.h>
#include <dlfcn.h>
void foo();
int main(int argc, char *argv[]) {
    void (*pfoo)();
    foo(); // call foo() first so we are sure ./preload/libfoo.so is loaded when we call dlopen()
    pfoo = dlsym(dlopen("libfoo.so", RTLD_NOW), "foo");
    pfoo();
    return 0;
}

命令行

LD_PRELOAD=preload/libfoo.so LD_LIBRARY_PATH=. ./a.out

产量

version 2
version 1

为什么LD_PRELOAD不影响dlopen(),有没有办法重定向dlopen(),特别是在使用绝对路径时?

最佳答案:

指定LD_PRELOAD将导致加载程序在加载主可执行文件之前无条件地加载(并初始化)指示的共享库.这使得在链接main之前在预加载的库中定义的符号可用,允许插入符号. [注1]

因此,在您的示例中,对foo()的调用使用预加载模块中的符号,如果您使用NULL句柄调用它,则dlsym将返回相同的符号.

但是,对dlopen的调用并未考虑您要查找的符号(出于显而易见的原因).它只是加载指示的共享对象或返回共享对象的已缓存版本的句柄.如果需要,它不会将模块添加到要加载的模块列表中;它只是加载模块.当您将返回的句柄传递给dlsym时,dlsym会精确查找该模块以解析符号,而不是搜索可执行文件中存在的外部符号集. [笔记2]

正如我所提到的,如果已经加载了对象,dlopen将不会多次加载“相同”的共享对象. [注3].但是,LD_PRELOAD中的共享对象称为preload / libfoo.so,而不是libfoo.so. (与其他操作系统不同,ELF不会从共享对象名中删除目录路径.)因此,当您调用dlopen(“libfoo.so”)时,动态加载程序不会在缓存中找到任何名为libfoo.so的共享对象加载共享对象,因此它将使用库搜索路径在文件系统中查找该对象,因为提供的文件名不包含/.

事实证明,ELF允许您指定共享对象的名称.因此,您可以将预加载模块的名称设置为稍后将动态加载的名称,然后dlopen将把句柄返回到预加载的模块.

我们首先在原始问题中更正main.c的版本:

#include <stdio.h>
#include <dlfcn.h>
void foo();
int main(int argc, char *argv[]) {
    const char* soname = argc > 1 ? argv[1] : "libfoo.so";
    void (*pfoo)();
    pfoo = dlsym(NULL, "foo"); // Find the preloaded symbol, if any.
    if (pfoo) pfoo(); else puts("No symbol foo before dlopen.");
    void* handle = dlopen(soname, RTLD_NOW);
    if (handle) {
      pfoo = dlsym(handle, "foo"); // Find the symbol in the loaded SO
      if (pfoo) pfoo(); else puts("No symbol foo after dlopen.");
    }
    else puts("dlopen failed to find the shared object.");
    return 0;
}

这可以在不指定除libdl之外的任何库的情况下构建:

gcc -Wall -o main main.c -ldl

如果我们构建没有指定名称的两个共享库,这可能就是你所做的:

gcc -Wall -o libfoo.so -shared -fPIC libfoo.c
gcc -Wall -o preload/libfoo.so -shared -fPIC preload/libfoo.c

然后我们观察到dlopen / dlsym在加载的模块中找到符号:

$LD_PRELOAD=preload/libfoo.so LD_LIBRARY_PATH=. ./main libfoo.so
version 2
version 1

但是,如果我们将要查找的名称分配给预加载的共享对象,我们会得到不同的行为:

$gcc -Wall -o preload/libfoo.so -Wl,--soname=libfoo.so -shared -fPIC preload/libfoo.c
$LD_PRELOAD=preload/libfoo.so LD_LIBRARY_PATH=. ./main libfoo.so
version 2
version 2

这是因为dlopen正在寻找名为libfoo.so的共享对象.但是,加载插件的应用程序更有可能使用文件名而不是使用库搜索路径.这将导致不考虑预加载的共享对象,因为名称不再匹配:

$LD_PRELOAD=preload/libfoo.so LD_LIBRARY_PATH=. ./main ./libfoo.so
version 2
version 1

碰巧,我们可以通过构建具有实际查找名称的共享库来完成此工作:

$gcc -Wall -o preload/libfoo.so -Wl,--soname=./libfoo.so -shared -fPIC preload/libfoo.c
$LD_PRELOAD=preload/libfoo.so LD_LIBRARY_PATH=. ./main libfoo.so
version 2
version 2

这是一个黑客攻击,恕我直言,但它可以接受调试. [注4]

笔记:

>因此,注释“首先调用foo(),所以我们确定./preload/libfoo.so已加载”是不正确的;预加载的模块是预加载的,如果需要,不会添加到要加载的模块列表中.
>如果您希望dlsym只查找符号,则可以传递NULL句柄.在这种情况下,dlsym将搜索由dlopen加载的模块(包括由dlopen加载的模块所需的模块).但这很少是你想要的,因为使用dlsym加载插件的应用程序通常会指定插件必须定义的特定符号(或符号),并且这些符号将出现在每个加载的插件中,使得符号名称的查找不精确.
>这不太正确,但动态符号命名空间超出了本答案的范围.
>当然,其他黑客也是可能的.例如,您可以插入自己的dlopen版本来覆盖共享对象名称查找.但这可能比必要的工作要多得多.

标签:c-3,linux,shared-libraries,dlopen,ld-preload
来源: https://codeday.me/bug/20190516/1115373.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有