【问题标题】:How can I verify what dynamic linker is used when a program is run?如何验证程序运行时使用的动态链接器?
【发布时间】:2020-12-23 18:41:22
【问题描述】:

我想验证程序运行时使用的动态链接器是通过filereadelf -lldd 提到的那个。我的动机源于在机器上的不同空间中存在多个动态链接器,它们不应该混合搭配。

到目前为止,我发现验证动态链接器的最佳方法是通过gdb。通过查看info proc mappings 的输出,我可以确定哪个动态链接器被映射到地址空间并正在使用中。我试图避免使用gdb,因为它需要我通过它运行测试套件和其他东西。

使用LD_DEBUG 环境变量似乎是一种替代解决方案,可以让我在程序执行之后(或期间)轻松保存日志以进行验证。但是,我不确定哪个选项会给我最好的信息。我认为scopeslibs 可能是不错的选择,但libs 并不总是提到动态链接器。例如,这是一个简单的 hello world 程序的输出:

$ LD_DEBUG=libs ./test0
     24579: find library=libc.so.6 [0]; searching
     24579:  search cache=/etc/ld.so.cache
     24579:   trying file=/lib/x86_64-linux-gnu/libc.so.6
     24579:
     24579:
     24579: calling init: /lib/x86_64-linux-gnu/libc.so.6
     24579:
     24579:
     24579: initialize program: ./test0
     24579:
     24579:
     24579: transferring control: ./test0
     24579:
hello world
     24579:
     24579: calling fini: ./test0 [0]
     24579:
$ LD_DEBUG=libs ./test0-gnu-cross
     24581: find library=libc.so.6 [0]; searching
     24581:  search path=/usr/local/gnu-cross/x86_64-linux-gnu/lib/glibc-hwcaps/x86-64-v4:/usr/local/gnu-cross/x86_64-linux-gnu/lib/glibc-hwcaps/x86-64-v3:/usr/local/gnu-cross/x86_64-linux-gnu/lib/glibc-hwcaps/x86-64-v2:/usr/local/gnu-cross/x86_64-linux-gnu/lib/tls/haswell/avx512_1/x86_64:/usr/local/gnu-cross/x86_64-linux-gnu/lib/tls/haswell/avx512_1:/usr/local/gnu-cross/x86_64-linux-gnu/lib/tls/haswell/x86_64:/usr/local/gnu-cross/x86_64-linux-gnu/lib/tls/haswell:/usr/local/gnu-cross/x86_64-linux-gnu/lib/tls/avx512_1/x86_64:/usr/local/gnu-cross/x86_64-linux-gnu/lib/tls/avx512_1:/usr/local/gnu-cross/x86_64-linux-gnu/lib/tls/x86_64:/usr/local/gnu-cross/x86_64-linux-gnu/lib/tls:/usr/local/gnu-cross/x86_64-linux-gnu/lib/haswell/avx512_1/x86_64:/usr/local/gnu-cross/x86_64-linux-gnu/lib/haswell/avx512_1:/usr/local/gnu-cross/x86_64-linux-gnu/lib/haswell/x86_64:/usr/local/gnu-cross/x86_64-linux-gnu/lib/haswell:/usr/local/gnu-cross/x86_64-linux-gnu/lib/avx512_1/x86_64:/usr/local/gnu-cross/x86_64-linux-gnu/lib/avx512_1:/usr/local/gnu-cross/x86_64-linux-gnu/lib/x86_64:/usr/local/gnu-cross/x86_64-linux-gnu/lib     (RPATH from file ./test0-gnu-cross)
     24581:   trying file=/usr/local/gnu-cross/x86_64-linux-gnu/lib/glibc-hwcaps/x86-64-v4/libc.so.6
     24581:   trying file=/usr/local/gnu-cross/x86_64-linux-gnu/lib/glibc-hwcaps/x86-64-v3/libc.so.6
     24581:   trying file=/usr/local/gnu-cross/x86_64-linux-gnu/lib/glibc-hwcaps/x86-64-v2/libc.so.6
     24581:   trying file=/usr/local/gnu-cross/x86_64-linux-gnu/lib/tls/haswell/avx512_1/x86_64/libc.so.6
     24581:   trying file=/usr/local/gnu-cross/x86_64-linux-gnu/lib/tls/haswell/avx512_1/libc.so.6
     24581:   trying file=/usr/local/gnu-cross/x86_64-linux-gnu/lib/tls/haswell/x86_64/libc.so.6
     24581:   trying file=/usr/local/gnu-cross/x86_64-linux-gnu/lib/tls/haswell/libc.so.6
     24581:   trying file=/usr/local/gnu-cross/x86_64-linux-gnu/lib/tls/avx512_1/x86_64/libc.so.6
     24581:   trying file=/usr/local/gnu-cross/x86_64-linux-gnu/lib/tls/avx512_1/libc.so.6
     24581:   trying file=/usr/local/gnu-cross/x86_64-linux-gnu/lib/tls/x86_64/libc.so.6
     24581:   trying file=/usr/local/gnu-cross/x86_64-linux-gnu/lib/tls/libc.so.6
     24581:   trying file=/usr/local/gnu-cross/x86_64-linux-gnu/lib/haswell/avx512_1/x86_64/libc.so.6
     24581:   trying file=/usr/local/gnu-cross/x86_64-linux-gnu/lib/haswell/avx512_1/libc.so.6
     24581:   trying file=/usr/local/gnu-cross/x86_64-linux-gnu/lib/haswell/x86_64/libc.so.6
     24581:   trying file=/usr/local/gnu-cross/x86_64-linux-gnu/lib/haswell/libc.so.6
     24581:   trying file=/usr/local/gnu-cross/x86_64-linux-gnu/lib/avx512_1/x86_64/libc.so.6
     24581:   trying file=/usr/local/gnu-cross/x86_64-linux-gnu/lib/avx512_1/libc.so.6
     24581:   trying file=/usr/local/gnu-cross/x86_64-linux-gnu/lib/x86_64/libc.so.6
     24581:   trying file=/usr/local/gnu-cross/x86_64-linux-gnu/lib/libc.so.6
     24581:
     24581:
     24581: calling init: /usr/local/gnu-cross/x86_64-linux-gnu/lib/ld-linux-x86-64.so.2
     24581:
     24581:
     24581: calling init: /usr/local/gnu-cross/x86_64-linux-gnu/lib/libc.so.6
     24581:
     24581:
     24581: initialize program: ./test0-gnu-cross
     24581:
     24581:
     24581: transferring control: ./test0-gnu-cross
     24581:
hello world
     24581:
     24581: calling fini: ./test0-gnu-cross [0]
     24581:

如您所见,使用标准 Debian/GNU 工具链构建并使用系统动态链接器的程序 test0 并没有说明这一点。

scopes 选项看起来更有帮助,但我不明白输出在说什么:

$ LD_DEBUG=scopes ./test0
     24577:
     24577: Initial object scopes
     24577: object=./test0 [0]
     24577:  scope 0: ./test0 /lib/x86_64-linux-gnu/libc.so.6 /lib64/ld-linux-x86-64.so.2
     24577:
     24577: object=linux-vdso.so.1 [0]
     24577:  scope 0: ./test0 /lib/x86_64-linux-gnu/libc.so.6 /lib64/ld-linux-x86-64.so.2
     24577:  scope 1: linux-vdso.so.1
     24577:
     24577: object=/lib/x86_64-linux-gnu/libc.so.6 [0]
     24577:  scope 0: ./test0 /lib/x86_64-linux-gnu/libc.so.6 /lib64/ld-linux-x86-64.so.2
     24577:
     24577: object=/lib64/ld-linux-x86-64.so.2 [0]
     24577:  no scope
     24577:
hello world
$ LD_DEBUG=scopes ./test0-gnu-cross
     24576:
     24576: Initial object scopes
     24576: object=./test0-gnu-cross [0]
     24576:  scope 0: ./test0-gnu-cross /usr/local/gnu-cross/x86_64-linux-gnu/lib/libc.so.6 /usr/local/gnu-cross/x86_64-linux-gnu/lib/ld-linux-x86-64.so.2
     24576:
     24576: object=linux-vdso.so.1 [0]
     24576:  scope 0: ./test0-gnu-cross /usr/local/gnu-cross/x86_64-linux-gnu/lib/libc.so.6 /usr/local/gnu-cross/x86_64-linux-gnu/lib/ld-linux-x86-64.so.2
     24576:  scope 1: linux-vdso.so.1
     24576:
     24576: object=/usr/local/gnu-cross/x86_64-linux-gnu/lib/libc.so.6 [0]
     24576:  scope 0: ./test0-gnu-cross /usr/local/gnu-cross/x86_64-linux-gnu/lib/libc.so.6 /usr/local/gnu-cross/x86_64-linux-gnu/lib/ld-linux-x86-64.so.2
     24576:
     24576: object=/usr/local/gnu-cross/x86_64-linux-gnu/lib/ld-linux-x86-64.so.2 [0]
     24576:  no scope
     24576:
hello world

总之,我想找到一种验证正在使用的动态链接器的好方法。除非您能想到更好的选择,否则LD_DEBUG 似乎是一个不错的选择,但我很难理解如何在这种情况下有效地使用它。

感谢您的帮助:)

【问题讨论】:

  • info proc mappings 似乎提供了/proc/self/maps 中包含的信息,因此仅阅读后者可能就足够了
  • @CraigEstey 使用该解决方案,我不必在程序终止之前竞相阅读/proc/<pid>/maps 吗?有没有办法预测接下来会生成什么 PID,然后在 /proc 中等待它的创建?

标签: c linux dynamic-linking


【解决方案1】:

没有需要实际运行可执行文件来确定它使用的ELF解释器。

我们可以使用静态工具,并保证我们可以得到完整的路径。

我们可以使用readelfldd 的组合。

如果我们使用readelf -a,我们可以解析输出。


readelf 输出的一部分:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         00000000000002e0  000002e0
       000000000000001c  0000000000000000   A       0     0     1

注意.interp 部分的地址。是0x2e0


如果我们打开可执行文件并查找该偏移量,我们可以读取 ELF 解释器字符串。例如,这里是[我会叫什么]fileBad

000002e0: 2F6C6962 36342F7A 642D6C69 6E75782D  /lib64/zd-linux-
000002f0: 7838362D 36342E73 6F2E3200 00000000  x86-64.so.2.....

请注意,字符串似乎有点奇怪......稍后会详细介绍......


在“程序标题:”部分下,我们有:

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                 0x00000000000002a0 0x00000000000002a0  R      0x8
  INTERP         0x00000000000002e0 0x00000000000002e0 0x00000000000002e0
                 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/zd-linux-x86-64.so.2]

再次注意0x2e0 文件偏移量。这可能是获取 ELF 解释器路径的更简单方法。

现在我们有了 ELF 解释器的完整路径。


我们现在可以执行ldd /path/to/executable,我们将获得它正在/将要使用的共享库的列表。我们将为fileGood 执行此操作。通常,这看起来像 [redacted]:

linux-vdso.so.1 (0x00007ffc96d43000)
libpython3.7m.so.1.0 => /lib64/libpython3.7m.so.1.0 (0x00007f36d1ee2000)
...
libc.so.6 => /lib64/libc.so.6 (0x00007f36d1ac7000)
/lib64/ld-linux-x86-64.so.2 (0x00007f36d23ff000)
...

这是一个普通的可执行文件。这是fileBadldd 输出:

linux-vdso.so.1 (0x00007ffc96d43000)
libpython3.7m.so.1.0 => /lib64/libpython3.7m.so.1.0 (0x00007f36d1ee2000)
...
libc.so.6 => /lib64/libc.so.6 (0x00007f36d1ac7000)
/lib64/zd-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007f3f4f821000)
...

好吧,解释一下……

fileGood 是标准可执行文件 [/bin/vi 在我的系统上]。但是,fileBad 是我在修补不存在文件的解释器路径处制作的副本

readelf 数据中,我们知道解释器路径。我们可以检查该文件是否存在。如果它不存在,那么事情[显然]很糟糕。

利用我们从readelf 得到的解释器路径,我们可以从ldd 中找到解释器的输出行。

对于好的文件,ldd 给了我们简单的解释器解析:

/lib64/ld-linux-x86-64.so.2 (0x00007f36d23ff000)

对于坏文件,ldd 给了我们:

/lib64/zd-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007f3f4f821000)

因此,ldd 或内核检测到缺少的解释器并替换了默认解释器。

如果我们尝试从 shell 执行 fileBad,我们会得到:

fileBad: Command not found

如果我们尝试从 C 程序执行 fileBad,我们会收到 ENOENT 错误:

No such file or directory

由此我们知道当我们进行exec* 系统调用时,内核确实没有尝试使用“默认”解释器。

所以,我们现在知道我们为确定 ELF 解释器路径所做的静态分析是有效的。

我们可以确信我们想出的路径 [将是]通向ELF解释器的路径kernel 将映射到进程地址空间。


为了进一步保证,如果需要,请下载内核源代码。查看文件:fs/binfmt_elf.c


我认为这就足够了,但要在您的置顶评论中回答问题

使用该解决方案,我是否不必在程序终止之前竞相阅读/proc/<pid>/maps

没有必要比赛

我们可以控制fork 进程。我们可以设置子进程在[系统调用]ptrace 下运行,这样我们就可以控制它的执行(注意ptracegdbstrace 使用的)。

在我们fork 之后,但在我们exec 之前,孩子可以请求exec 的目标休眠,直到一个进程通过ptrace 附加到它。

因此,父级可以在目标可执行文件执行一条指令之前检查/proc/pid/maps [或其他任何内容]。它可以通过ptrace 控制执行[并最终分离以允许目标正常运行]。

有没有办法预测接下来会生成什么 PID,然后在 /proc 中等待它的创建?

鉴于您问题第一部分的答案,这有点争议。

没有方法可以[准确地]预测我们fork 的进程的pid。如果我们可以确定系统将使用下一个pid,则保证我们会赢得比赛 反对另一个进程执行fork [在我们之前] 并“获取”我们“认为”将属于我们的pid

【讨论】:

  • 感谢@CraigEstey 的精心回答。你是对的,帮助我得出了一个不同的、互补的结论。我使用的是系统的ldd,而不是我的交叉编译器的ldd。交叉编译的ldd 一直说not a dynamic executable,这是由于ldd 中的RTLD 路径没有指向我在交叉编译路径中的正确动态链接器。我想知道这条路径是否不正确,因为我滥用了--prefix 或我的工具链构建中的某些内容。
【解决方案2】:

您可以为此使用LD_DEBUG=scopes

我机器的示例输出:

LD_DEBUG=scopes ./hello
     17513:
     17513:     Initial object scopes
     17513:     object=./hello [0]
     17513:      scope 0: ./hello /lib/x86_64-linux-gnu/libc.so.6 /lib64/ld-linux-x86-64.so.2
     17513:
     17513:     object=linux-vdso.so.1 [0]
     17513:      scope 0: ./hello /lib/x86_64-linux-gnu/libc.so.6 /lib64/ld-linux-x86-64.so.2
     17513:      scope 1: linux-vdso.so.1
     17513:
     17513:     object=/lib/x86_64-linux-gnu/libc.so.6 [0]
     17513:      scope 0: ./hello /lib/x86_64-linux-gnu/libc.so.6 /lib64/ld-linux-x86-64.so.2
     17513:
     17513:     object=/lib64/ld-linux-x86-64.so.2 [0]
     17513:      no scope
     17513:
Hello world

寻找没有范围的对象。 此外,LD_DEBUG 只有几个值,请检查它们here 并进行实验。

【讨论】:

  • 您能否详细说明一下范围选项以及为什么“无范围”意味着它是动态链接器?
  • @peachykeen 看起来好像范围是由动态链接器确定的,所以动态链接器没有范围,因为它不是由动态链接器加载的。
  • 我有一个不响应 LD_DEBUG 的动态链接器。
  • @Joshua 最明显的做法是在您正在使用的程序上运行file readelf -lldd(甚至hexdump)命令执行并查看它正在使用哪个链接器,但 OP 已经提到过。
  • @UnslanderMonica 感谢您的加入。范围是否定义了一个对象(如上所示)可以在哪里解析其符号?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-02-26
  • 1970-01-01
  • 2017-06-10
  • 2017-03-28
  • 2019-12-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多