“静态链接"和“静态链接"有什么区别?和“不是动态可执行文件"来自 Linux ldd? [英] What's the difference between "statically linked" and "not a dynamic executable" from Linux ldd?

查看:46
本文介绍了“静态链接"和“静态链接"有什么区别?和“不是动态可执行文件"来自 Linux ldd?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑这个 AMD64 汇编程序:

Consider this AMD64 assembly program:

.globl _start
_start:
    xorl %edi, %edi
    movl $60, %eax
    syscall

如果我用 gcc -nostdlib 编译它并运行 ldd a.out,我得到这个:

If I compile that with gcc -nostdlib and run ldd a.out, I get this:

        statically linked

如果我改为使用 gcc -static -nostdlib 编译它并运行 ldd a.out,我会得到:

If I instead compile that with gcc -static -nostdlib and run ldd a.out, I get this:

        not a dynamic executable

静态链接非动态可执行文件有什么区别?如果我的二进制文件已经静态链接,为什么添加 -static 会影响任何事情?

What's the difference between statically linked and not a dynamic executable? And if my binary was already statically linked, why does adding -static affect anything?

推荐答案

这里有两个独立的事情:

There are two separate things here:

  • 是否请求 ELF 解释器 (ld.so).
    类似于 #!/bin/sh 但对于二进制文件,在您的 _start 之前运行.
    这是静态与动态可执行之间的区别.
  • 用于加载 ld.so 的动态链接库列表恰好是空的.
    这显然是 ldd 所说的静态链接",即您在构建时可能链接的任何库都是静态库.
  • Requesting an ELF interpreter (ld.so) or not.
    Like #!/bin/sh but for binaries, runs before your _start.
    This is the difference between a static vs. dynamic executable.
  • The list of dynamically linked libraries for ld.so to load happens to be empty.
    This is apparently what ldd calls "statically linked", i.e. that any libraries you might have linked at build time were static libraries.

filereadelf 等其他工具提供更多信息并使用符合您期望的术语.

Other tools like file and readelf give more information and use terminology that matches what you'd expect.

您的 GCC 是这样配置-pie 是默认值,gcc 不会为没有动态库的特殊情况制作静态饼图.

Your GCC is configured so -pie is the default, and gcc doesn't make a static-pie for the special case of no dynamic libraries.

  • gcc -nostdlib 只是制作一个 PIE,它碰巧没有链接到任何库,但在其他方面与普通 PIE 相同,指定了一个 ELF 解释器.
    ldd 混淆地称之为静态链接".
    file : ELF 64 位 LSB pie 可执行文件,x86-64,版本 1 (SYSV),动态链接,解释器/lib64/ld-linux-x86-64.so.2 ...
  • gcc -nostdlib -static 覆盖 -pie 默认值并生成真正的静态可执行文件.
    file : ELF 64 位 LSB 可执行文件,x86-64,版本 1 (SYSV),静态链接 ...
  • gcc -nostdlib -no-pie 也选择制作静态可执行文件作为根本没有动态库的情况的优化.由于无论如何都无法对非 PIE 可执行文件进行 ASLR,因此这是有道理的.逐字节与 -static 情况相同.
  • gcc -nostdlib -static-pie 制作一个不需要 ELF 解释器的 ASLRable 可执行文件.默认情况下,GCC 不会为 gcc -pie -nostdlib 执行此操作,这与 no-pie 情况不同,它在没有动态链接库时选择回避 ld.so涉及.
    file : ELF 64 位 LSB pie 可执行文件,x86-64,版本 1 (SYSV),静态链接 ...

  • gcc -nostdlib just makes a PIE that happens not to link to any libraries but is otherwise identical to a normal PIE, specifying an ELF interpreter.
    ldd confusingly calls this "statically linked".
    file : ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2 ...
  • gcc -nostdlib -static overrides the -pie default and makes a true static executable.
    file : ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked ...
  • gcc -nostdlib -no-pie also chooses to make a static executable as an optimization for the case where there are no dynamic libraries at all. Since a non-PIE executable couldn't have been ASLRed anyway, this makes sense. Byte-for-byte identical to the -static case.
  • gcc -nostdlib -static-pie makes an ASLRable executable that doesn't need an ELF interpreter. GCC doesn't do this by default for gcc -pie -nostdlib, unlike the no-pie case where it chooses to sidestep ld.so when no dynamically-linked libraries are involved.
    file : ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), statically linked ...

-static-pie 晦涩难懂,很少使用,而且较旧的 file 无法将其识别为静态链接.

-static-pie is obscure, rarely used, and older file doesn't identify it as statically linked.

-nostdlib 并不意味着 -no-pie-static-static-pie必须明确指定才能获得.

-nostdlib doesn't imply -no-pie or -static, and -static-pie has to be explicitly specified to get that.

gcc -static-pie 调用 ld -static -pie,所以 ld 必须知道这意味着什么.与您不必明确要求动态可执行文件的非 PIE 情况不同,如果您传递 ld 任何 .so 库,您只需得到一个.我认为这就是为什么您碰巧从 gcc -nostdlib -no-pie 获得静态可执行文件的原因 - GCC 不必做任何特别的事情,它只是 ld 进行优化.

gcc -static-pie invokes ld -static -pie, so ld has to know what that means. Unlike with the non-PIE case where you don't have to ask for a dynamic executable explicitly, you just get one if you pass ld any .so libraries. I think that's why you happen to get a static executable from gcc -nostdlib -no-pie - GCC doesn't have to do anything special, it's just ld doing that optimization.

但是 ld 不会在指定 -pie 时隐式启用 -static,即使没有要链接的共享库.

But ld doesn't enable -static implicitly when -pie is specified, even when there are no shared libraries to link.

使用 gcc --version 生成的示例 gcc (Arch Linux 9.3.0-1) 9.3.0
ld --version GNU ld (GNU Binutils) 2.34(readelf 也是 binutils)
ldd --version ldd (GNU libc) 2.31
file --version file-5.38 - 请注意静态饼图检测在最近的补丁中发生了变化,Ubuntu 挑选了一个未发布的补丁.(感谢@Joseph 的侦探工作) - this in 2019 检测到动态= 有一个 PT_INTERP 来处理静态饼图,但 恢复基于 PT_DYNAMIC 检测所以共享库算作dynamic.debian 错误 #948269.static-pie 是一个鲜为人知的很少使用的功能.

Examples generated with gcc --version gcc (Arch Linux 9.3.0-1) 9.3.0
ld --version GNU ld (GNU Binutils) 2.34 (also readelf is binutils)
ldd --version ldd (GNU libc) 2.31
file --version file-5.38 - note that static-pie detection has changed in recent patches, with Ubuntu cherry-picking an unreleased patch. (Thanks @Joseph for the detective work) - this in 2019 detected dynamic = having a PT_INTERP to handle static-pie, but it was reverted to detect based on PT_DYNAMIC so shared libraries count as dynamic. debian bug #948269. static-pie is an obscure rarely-used feature.

GCC 最终运行 ld -pie exit.o 并指定了动态链接器路径,并且没有库.(还有一大堆其他选项来支持可能的 LTO 链接时间优化,但这里的关键是 -dynamic-linker/lib64/ld-linux-x86-64.so.2 -pie.collect2 只是对 ld 的包装.)

GCC ends up running ld -pie exit.o with a dynamic linker path specified, and no libraries. (And a boatload of other options to support possible LTO link-time optimization, but the keys here are -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie. collect2 is just a wrapper around ld.)

$ gcc -nostdlib exit.s -v      # output manually line wrapped with  for readability
...
COLLECT_GCC_OPTIONS='-nostdlib' '-v' '-mtune=generic' '-march=x86-64'
 /usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/collect2  
-plugin /usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/liblto_plugin.so 
-plugin-opt=/usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/lto-wrapper 
-plugin-opt=-fresolution=/tmp/ccoNx1IR.res 
--build-id --eh-frame-hdr --hash-style=gnu 
-m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie 
-L/usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0 
-L/usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/../../../../lib -L/lib/../lib 
-L/usr/lib/../lib 
-L/usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/../../.. 
/tmp/cctm2fSS.o

您将获得一个不依赖于其他库的动态 PIE.运行它仍然会调用ELF 解释器"/lib64/ld-linux-x86-64.so.2,它会在跳转到你的 _start 之前运行.(尽管内核已经将可执行文件的 ELF 段映射到 ASLRed 虚拟地址,以及 ld.so 的 text/data/bss).

You get a dynamic PIE with no dependencies on other libraries. Running it still invokes the "ELF interpreter" /lib64/ld-linux-x86-64.so.2 on it which runs before jumping to your _start. (Although the kernel has already mapped the executable's ELF segments to ASLRed virtual addresses, along with ld.so's text / data / bss).

file 和 readelf 更具描述性.

file and readelf are more descriptive.

$ gcc -nostdlib exit.s -o exit-default
$ ls -l exit-default 
-rwxr-xr-x 1 peter peter 13536 May  2 02:15 exit-default 
$ ldd exit-default 
        statically linked
$ file exit-default
exit-default: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=05a4d1bdbc94d6f91cca1c9c26314e1aa227a3a5, not stripped

$ readelf -a exit-default
...
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x1000
...
Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                 0x00000000000001f8 0x00000000000001f8  R      0x8
  INTERP         0x0000000000000238 0x0000000000000238 0x0000000000000238
                 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x00000000000002b1 0x00000000000002b1  R      0x1000
  LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000
                 0x0000000000000009 0x0000000000000009  R E    0x1000
  ...   (the Read+Exec segment to be mapped at virt addr 0x1000 is where your text section was linked.)

如果你跟踪它,你也可以看到差异:

If you strace it you can also see the differences:

$ gcc -nostdlib exit.s -o exit-default
$ strace ./exit-default
execve("./exit-default", ["./exit-default"], 0x7ffe1f526040 /* 51 vars */) = 0
brk(NULL)                               = 0x5617eb1e4000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffcea703380) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9ff5b3e000
arch_prctl(ARCH_SET_FS, 0x7f9ff5b3ea80) = 0
mprotect(0x5617eabac000, 4096, PROT_READ) = 0
exit(0)                                 = ?
+++ exited with 0 +++

对比-static-static-pie 在用户空间中执行的第一条指令是您的 _start(您也可以使用 开始).

vs. -static and -static-pie the first instruction executed in user-space is your _start (which you can also check with GDB using starti).

$ strace ./exit-static-pie 
execve("./exit-static-pie", ["./exit-static-pie"], 0x7ffcdac96dd0 /* 51 vars */) = 0
exit(0)                                 = ?
+++ exited with 0 +++

<小时>

gcc -nostdlib -static-pie

$ gcc -nostdlib -static-pie exit.s -o exit-static-pie
$ ls -l exit-static-pie
-rwxr-xr-x 1 peter peter 13440 May  2 02:18 exit-static-pie
peter@volta:/tmp$ ldd exit-static-pie
        statically linked
peter@volta:/tmp$ file exit-static-pie
exit-static-pie: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=daeb4a8f11bec1bb1aaa13cd48d24b5795af638e, not stripped

$ readelf -a exit-static-pie 
...
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x1000
...

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000229 0x0000000000000229  R      0x1000
  LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000
                 0x0000000000000009 0x0000000000000009  R E    0x1000
  ... (no Interp header, but still a read+exec text segment)

请注意,地址仍然是相对于映像基址的,将 ASLR 留给内核.

Notice that the addresses are still relative to the image base, leaving ASLR up to the kernel.

令人惊讶的是,ldd 并没有说它不是动态可执行文件.这可能是一个错误,或者某些实现细节的副作用.

Surprisingly, ldd doesn't say that it's not a dynamic executable. That might be a bug, or a side effect of some implementation detail.

$ gcc -nostdlib -static exit.s -o exit-static
$ ls -l exit-static
-rwxr-xr-x 1 peter peter 4744 May  2 02:26 exit-static
peter@volta:/tmp$ ldd exit-static
        not a dynamic executable
peter@volta:/tmp$ file exit-static
exit-static: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=1b03e3d05709b7288fe3006b4696fd0c11fb1cb2, not stripped
peter@volta:/tmp$ readelf -a exit-static
ELF Header:
...
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x401000
...   (Note the absolute entry-point address nailed down at link time)
      (And that the ELF type is EXEC, not DYN)

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x000000000000010c 0x000000000000010c  R      0x1000
  LOAD           0x0000000000001000 0x0000000000401000 0x0000000000401000
                 0x0000000000000009 0x0000000000000009  R E    0x1000
  NOTE           0x00000000000000e8 0x00000000004000e8 0x00000000004000e8
                 0x0000000000000024 0x0000000000000024  R      0x4

 Section to Segment mapping:
  Segment Sections...
   00     .note.gnu.build-id 
   01     .text 
   02     .note.gnu.build-id 
   ...

那些是所有的程序头;与 pie/static-pie 不同,我不会遗漏任何内容,只是 readelf -a 输出的其他整个部分.

Those are all the program headers; unlike pie / static-pie I'm not leaving any out, just other whole parts of the readelf -a output.

还要注意程序头中的绝对虚拟地址,它不会让内核选择在虚拟地址空间中映射文件的位置.这是 ELF 对象的 EXEC 和 DYN 类型之间的区别.PIE 可执行文件是具有入口点的共享对象,允许我们获取主可执行文件的 ASLR.实际的 EXEC 可执行文件具有链接时选择的内存布局.

Also note the absolute virtual addresses in the program headers that don't give the kernel a choice where in virtual address space to map the file. This is the difference between EXEC and DYN types of ELF objects. PIE executables are shared objects with an entry point, allowing us to get ASLR for the main executable. Actual EXEC executables have a link-time-chosen memory layout.

ldd 显然只在以下两种情况下才报告不是动态可执行文件":

ldd apparently only reports "not a dynamic executable" when both:

  • 没有 ELF 解释器(动态链接器)路径
  • ELF 类型 = EXEC

这篇关于“静态链接"和“静态链接"有什么区别?和“不是动态可执行文件"来自 Linux ldd?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆