如果我直接使用共享库中的函数,即在我的代码中声明它并在编译期间链接,LD_PRELOAD工作正常。但是,如果我使用/**
* @ORM\Table(name="catalogue_niveau")
* @ORM\Entity(repositoryClass="AppBundle\Entity\CatalogLevelRepository")
*/
class CatalogLevel
{
/**
* @ORM\Column(name="Niveau_ID", type="string", length=15)
* @ORM\Id
*/
private $id;
/**
* @ORM\Column(name="Catalogue_Code", type="string", length=15)
* @ORM\Id
*/
private $catalogCode;
/**
* @ORM\ManyToOne(targetEntity="Catalog", inversedBy="levels")
* @ORM\JoinColumn(name="Catalogue_Code", referencedColumnName="Catalogue_Code")
*/
private $catalog;
/**
* Set id
*
* @param string $id
*
* @return CatalogLevel
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
/**
* Get id
*
* @return string
*/
public function getId()
{
return $this->id;
}
/**
* Set catalogCode
*
* @param string $catalogCode
*
* @return CatalogLevel
*/
public function setCatalogCode($catalogCode)
{
$this->catalogCode = $catalogCode;
return $this;
}
/**
* Get catalogCode
*
* @return string
*/
public function getCatalogCode()
{
return $this->catalogCode;
}
}
/ dlopen()
而不是LD_PRELOAD没有效果!
问题是我想调试一个使用dlsym()
加载某些插件的程序,并且它使用绝对文件名,因此只使用dlopen()
将无效。
这是一个说明问题的示例代码。
LD_LIBRARY_PATH
void foo() {
printf("version 1\n");
}
void foo() {
printf("version 2\n");
}
LD_PRELOAD=preload/libfoo.so LD_LIBRARY_PATH=. ./a.out
version 2 version 1
为什么LD_PRELOAD不影响#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;
}
,有没有办法重定向dlopen()
,尤其是在使用绝对路径时?
答案 0 :(得分:4)
指定LD_PRELOAD
将导致加载程序在加载主可执行文件之前无条件地加载(并初始化)指示的共享库。这使得在链接main
之前预加载库中定义的符号可用,允许插入符号。 [注1]
因此,在您的示例中,对foo()
的调用使用预加载模块中的符号,如果您使用“{1}}调用它,dlsym
将返回相同的符号NULL句柄。
但是,对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已加载”是不正确的;预加载的模块 pre 已加载,未添加到要加载的模块列表中。
如果您希望dlsym
只查找符号,则可以传递NULL
句柄。在这种情况下,dlsym
将搜索由dlopen
加载的模块(包括由dlopen
加载的模块所需的模块)。但这很少是你想要的,因为用dlsym
加载插件的应用程序通常指定插件必须定义的特定符号(或符号),并且这些符号将出现在每个加载的插件中,使得按符号名称查找不精确的。
这不太正确,但动态符号命名空间超出了本答案的范围。
当然,其他黑客也是可能的。例如,您可以插入自己的dlopen
版本来覆盖共享对象名称查找。但这可能比必要的工作多得多。
答案 1 :(得分:1)
根据http://linux.die.net/man/3/dlopen
四个函数dlopen(),dlsym(),dlclose(),dlerror()实现 动态链接加载器的接口。
而LD_PRELOAD
仅影响动态链接器本身 - 即:ld.so(http://linux.die.net/man/8/ld.so)。我可以考虑强制dlopen
根据需要解决的唯一方法是chroot
。
跟进思想:
我刚想到的另一个想法,如果你编写一个FIRST加载正确*.so
的包装器,那么会调用你试图重定向的程序。这是否会导致子进程使用重定向的*.so
?
答案 2 :(得分:1)
问题是我想调试一个使用dlopen()加载一些插件的程序,并且它使用绝对文件名,所以简单地使用
LD_LIBRARY_PATH
将不起作用。
是的,dlopen
没有LD_LIBRARY_PATH
搜索带斜杠的路径。
您可以覆盖/叠加dlopen
本身来搜索这些特定的插件路径。