有可靠的方法知道哪些库可以在一个 ELF 二进制文件中进行 dlopen() 吗?

5

基本上,我想获得一个二进制文件可能加载的库列表。

我想出的不可靠方法似乎可以工作(可能会有误报):

comm -13 <(ldd elf_file | sed 's|\s*\([^ ]*\)\s.*|\1|'| sort -u) <(strings -a elf_file | egrep '^(|.*/)lib[^:/]*\.so(|\.[0-9]+)$' | sort -u)

这并不可靠,但即使二进制文件已被剥离,它仍然提供有用的信息。有没有一种可靠的方法可以得到这些信息而没有可能出现假阳性?
编辑:更多上下文。
Firefox正在从使用gstreamer过渡到使用ffmpeg。我想知道哪些版本的libavcodec.so将起作用。libxul.so针对许多可选功能使用dlopen()。而库名称是硬编码的。所以,以上命令在这种情况下很有帮助。
我还对软件包管理和二进制依赖性有一般性的兴趣。我知道你可以使用readelf -d获取直接依赖项,使用ldd获取依赖项的依赖项。我想知道可选依赖关系,因此问了这个问题。

3
dlopen() 载入的库不一定会在 ldd 的输出中出现,这并不影响它们被正确地加载和链接。 - mah
4
我如果使用"dlopen(argv[i])",你将如何发现? - Kerrek SB
1
以下是一个相当可靠的方法:find / |xargs file |grep 'ELF .* shared object' - nwellnhof
1
你为什么要问呢?你想列出哪个特定二进制文件的潜在插件? - Basile Starynkevitch
1
@NotImportant:对于这样的问题,首先要考虑的是:这个问题是否等价于停机问题(我的答案解释了它是)。 - Basile Starynkevitch
显示剩余3条评论
2个回答

8

ldd 告诉你二进制文件链接的库。这些不是程序可以使用 dlopen 打开的库。

dlopen 的签名为:

void *dlopen(const char *filename, int flag);

因此,您仍然无法可靠地在二进制文件上运行strings,但是如果库名称不是静态字符串而是在程序执行期间从某个地方构建或读取,则这仍然可能失败 - 这最后一种情况意味着您的问题的答案是“不”...... 不可靠。(例如,库文件的名称可以从网络,Unix套接字甚至是即时解压缩中读取。任何事情都有可能!-尽管我自己不推荐这些想法...)

编辑:正如John Bollinger所提到的那样,库名称可以从配置文件中读取。

编辑:您还可以尝试用您的系统调用之一替换dlopen(例如,Boehm垃圾收集器使用malloc),以便打开库,但也在某个地方记录其名称。 但是,如果程序在执行过程中没有打开特定的库,则仍将不知道它。


2
更为常见和合理的做法是从配置文件中读取可加载模块的名称。我的猜测是,最常见的情况是使用dlopen()打开那些在二进制文件中不作为字符串字面量出现的模块。 - John Bollinger
1
谢谢你的回答。所以,假设库名称是硬编码的,strings 是最好的选择,对吗? - Not Important
1
关于您的第二个编辑:1-它需要执行,这有些违背了初衷,因为我们可以跟踪运行的进程。2-不能保证我们将穿过调用dlopen()的所有代码路径。 - Not Important
@不重要:是的,我提到了<如果程序在执行过程中没有打开特定库,则仍然无法得知> -- 因此,并非所有代码路径都会被覆盖。 - Jay
@不重要:strings 是一种可能性。尝试查找配置文件和跟踪程序也可能会有很大帮助! - Jay

3

我关注的是Linux;我猜我的大部分答案适用于每个POSIX系统;但在MacOSX上,dlopen需要 .dylib 动态库 文件,而不是 .so 共享对象。

一个程序甚至可以在某个临时文件 /tmp/foo1234.c 中发出一些 C 代码,通过某个 gcc -O -shared -fPIC /tmp/foo1234.c -o /tmp/foo1234.so 命令将该 /tmp/foo1234.c 编译成共享库 /tmp/foo1234.so 并在程序运行时生成和执行,然后也许会删除 /tmp/foo1234.c 文件(因为它不再需要),并 dlopen 那个 /tmp/foo1234.so(也许在 dlopen 后甚至会删除 /tmp/foo1234.so),所有这些都在同一个进程中完成。我的 GCC MELT 插件和 Bigloo 都是这样做的,GCCJIT 库正在做类似的事情。

总的来说,你的任务是不可能的,甚至没有意义。

有没有一种可靠的方法可以获取这些信息而不会出现假阳性?

不,没有可靠的方法可以获取这样的信息而不会出现假阳性(你可以证明等同于停机问题,或者其他不可判定问题)。另请参见 Rice定理

在实践中,大多数dlopen发生在某些配置提供的插件上。在配置文件中可能没有确切地命名为这样(例如,一些Foo程序可能有一个约定,即在某个foo.conf配置文件中命名为bar的插件由foo-bar.so插件提供)。

然而,您可能会发现一些启发式的近似方法。大多数执行某些dlopen的程序在插件中有一些请求特定符号名称的插件约定。您可以搜索定义这些名称的共享对象。当然,您会得到假阳性。
例如,zsh shell接受名为zsh模块的插件。example模块显示期望在zsh模块中使用enables_boot_features_等函数。您可以使用nm -D查找提供这些文件(因此找到可能由zsh加载的插件)。
我不认为这样的方法是值得的;实际上,你通常应该知道哪些插件适用于你的系统,根据不同的应用程序进行选择。

顺便提一下,你可以在某个命令的执行过程中使用strace(1)来了解它所做的系统调用,以及它加载的插件。你也可以使用ltrace(1)pmap(1)(对于某个特定的进程),或者简单地使用cat /proc/1234/maps来了解它的虚拟地址空间,以及它已经加载的插件-对于一个进程 1234。请参见proc(5)

注意,在 Linux 上存在 straceltracepmap,但许多 POSIX 系统都有类似的程序。
此外,程序可以在运行时生成一些机器代码并执行它(SBCL 在每个REPL交互中都这样做!)。您的程序还可以使用一些 JIT 技术(例如 libjitllvmasmjitGCCJIT 或手写代码...)来实现类似的功能。因此,插件式行为可以在没有 dlopen 的情况下发生(您可以使用 mmap 调用和一些 ELF 重定位处理来模仿 dlopen)。

补充:

如果你正在安装火狐浏览器的打包版本(例如Debian上的iceweasel包),那么它的包很可能会处理依赖关系。


我相信程序甚至可以在dlopen()之后删除.so文件,不是吗?或者即使隐含地,POSIX也要求在dlopen()之后.so文件仍然可用吗? - Jay
1
@JayпјҡдҪ иҜҙеҫ—еҜ№гҖӮ.soж–Ү件еңЁdlopenд№ӢеҗҺз”ҡиҮіеҸҜд»Ҙиў«еҲ йҷӨгҖӮдҪҶжҳҜ/proc/1234/mapsдјҡжҳҫзӨәзӣёе…ідҝЎжҒҜгҖӮ - Basile Starynkevitch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接