如何使用 libtool/libltdl 的 dlpreopening / preloading?

How does one use libtool/libltdl's dlpreopening / preloading?

这是我想使用 libtool 的 libltdl dlpreopening 的代码类型的粗略示例:

https://github.com/EmmaJaneBonestell/dlopen-sample

我希望能够重写各种使用 libdl 函数(dlopen、dlsym 等)的项目,而不是使用 libtool 的 libltdl dlpreopening/preloading 机制。根据 libtool 的文档,它会在编译时 link 对象,从而产生真正的静态可执行文件。它适用于不支持动态加载的系统。

如果您使用它来编译和 link 这些对象,libtool 将 运行 它的一些脚本,并创建必要的数据结构,并且我相信执行 mangling 和 demangling,以允许符号名称重复。

即使查看了 libtool 源代码中的示例,阅读了它的整个文档,并查看了测试套件中的相关测试(例如测试 118 和 120),我仍然无法这样做。

在 libtool 本身之外,基本上我能找到的任何地方都没有提及此功能。我确实设法看到它在 Graphviz 中以对我来说太复杂的方式使用,但也很少有相关文档。

我真的算不上一个程序员,更像是一个修补匠。我很容易将 libltdl 用作标准 dlopen/dlsym.

的包装器

我认为我的主要问题是弄清楚如何 return 为预打开的对象提供正确的句柄,尽管我可能做错了其他事情。

文档指出 lt_dlopen 可以在预加载的静态模块上工作,但它似乎不接受我能想象到的任何可能的引用。

我也宁愿不必调用 libtool,但这就是生活。

我没有费心包括我在代码方面尝试过的任何东西(在示例中),因为我已经尝试了很多东西,我觉得展示它真的会让人感到困惑。

当前的 libtool 文档: https://www.gnu.org/software/libtool/manual/libtool.html

显然 libtool 需要 .la 文件存在并正确声明其 rpath,即使它不会用于此真正静态二进制文件中的任何内容。

Libtool 的文档非常清楚地表明您的“模块”文件应该包含以下用于所有要导出的符号的预处理器宏。

#define SYMBOL SOURCEFILENAME_LTX_SYMBOL

在我的例子中,这导致 bromine.c 和 chlorine.c 分别具有以下内容。

#define chemical_name bromine_LTX_chemical_name
#define chemical_name chlorine_LTX_chemical_name

仍然假设我的问题中的代码存储库,以下命令是如何使用 libtool 自动 dlpreopen 二进制文件的示例。

libtool --mode=compile gcc -static -fPIC -fno-plt -c bromine.c

libtool --mode=compile gcc -static -fPIC -fno-plt -c chlorine.c

libtool --mode=link gcc -all-static -fPIC -fno-plt -o bromine.la -rpath $PWD -module -no-undefined -avoid-version bromine.lo

libtool --mode=link gcc -all-static -fPIC -fno-plt -o chlorine.la -rpath $PWD -module -no-undefined -avoid-version chlorine.lo

然而,对我来说,跳过每一个 libtool 步骤并简单地在 main.c 本身内声明所需的结构数组要简单得多。将重新定义的 functions/symbols 声明为 extern int,而不考虑它们的类型,并创建一个名为 lt__PROGRAM__LTX_preloaded_symbols 的 lt_dlsymlist 数组。第一个元素是程序本身,最后一个元素是 {0, (void *) 0}。在这些之间,一个结构行将声明 dlpreopened object/static 存档的基本名称,文件后缀为“.a”。

它将有 .a 后缀不管它是 .a、.o、.c 还是任何其他文件.下一个结构将是您希望使用的从中导出的所有符号,然后,如果您愿意,还有另一个文件名、它的符号等等。符号名称将使用 (void *) &SYMBOL 而不是所有其他字段使用的 (void *) 0。

extern int bromine_LTX_chemical_name();
extern int chlorine_LTX_chemical_name();

const lt_dlsymlist lt__PROGRAM__LTX_preloaded_symbols[] ={ 
  {"@PROGRAM@", (void *) 0},
  {"chlorine.a", (void *) 0},
  {"chlorine_LTX_chemical_name", (void *) &chlorine_LTX_chemical_name},
  {"bromine.a", (void *) 0},
  {"bromine_LTX_chemical_name", (void *) &bromine_LTX_chemical_name},
  {0, (void *) 0}
};

最后,lt_dlopen 不反映 lt_dlsymlist 声明——理想情况下,您将只调用文件的基本名称,而不调用它的扩展名。例如

handle = lt_dlopen("chlorine");

handle = lt_dlopen("chlorine.a")

但是,我相信无论您提供什么扩展,它都会起作用,因为 lt_dlopen 函数会删除扩展。

按照上面规定的方式进行可以将编译减少到一行并完全避免使用 libtool。例如

gcc -static -Wl,-static -fPIC -fno-plt -Wl,-z,now,--no-undefined -o main main.c bromine.c chlorine.c -lltdl -ldl

我还通过链接到 libltdl 的调试版本并从中删除了预打开(没有 libdl 等),验证了这会产生一个真正的静态二进制文件。

$./main HOBr HOCl

loaders: lt_preopen
try_dlopen (bromine, (null))
tryall_dlopen (bromine.a, lt_preopen)
Calling lt_preopen->module_open (bromine.a)
  Result: Success
try_dlopen (chlorine, (null))
tryall_dlopen (chlorine.a, lt_preopen)
Calling lt_preopen->module_open (chlorine.a)
  Result: Success
The IUPAC name of HOBr is hypobromous acid.
The IUPAC name of HOCl is hypochlorous acid.