考虑以下AMD64汇编程序:

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

如果我使用gcc -nostdlib进行编译并运行ldd a.out,则会得到以下信息:
        statically linked

如果我改为使用gcc -static -nostdlib编译并运行ldd a.out,则会得到以下信息:
        not a dynamic executable
statically linkednot a dynamic executable有什么区别?如果我的二进制文件已经静态链接,为什么添加-static会产生任何影响?

最佳答案

这里有两件事:

  • 是否请求ELF解释器(ld.so)。
    #!/bin/sh类似,但对于二进制文件,应在_start之前运行。
    这是静态可执行文件与动态可执行文件之间的区别。
  • 供ld.so加载的动态链接库列表恰好为空。
    显然,这是ldd所称的“静态链接”,即您在构建时可能链接的任何库都是静态库。

  • 诸如filereadelf之类的其他工具可提供更多信息,并使用与您期望的术语相匹配的术语。

    您的GCC是configured so -pie is the default,对于没有动态库的特殊情况,gcc不会产生静态派。
  • gcc -nostdlib只是使一个PIE碰巧不链接到任何库,但在其他方面与普通PIE相同,但指定了ELF解释器。
    ldd令人困惑地称其为“静态链接”。 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会覆盖-pie的默认值,并使其成为真正的静态可执行文件。file:ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked ...
  • gcc -nostdlib -no-pie还选择使静态可执行文件作为根本没有动态库的情况的优化。由于无论如何都无法对非PIE可执行文件进行ASLR处理,因此这很有意义。逐字节与-static的情况相同。
  • gcc -nostdlib -static-pie 使ASLRable可执行文件不需要ELF解释器。默认情况下,GCC不会对gcc -pie -nostdlib执行此操作,这与无派情况不同,在这种情况下,当不涉及动态链接库时,它会选择回避ld.sofile:ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), statically linked ...-static-pie晦涩难懂,很少使用,并且较旧的file不会将其标识为静态链接。
  • -nostdlib并不意味着-no-pie-static,必须明确指定-static-pie才能实现。
    gcc -static-pie调用ld -static -pie,因此ld必须知道这意味着什么。与不需要显式请求动态可执行文件的非PIE情况不同,如果将ld传递给任何.so库,则只会得到一个。我认为这就是为什么您碰巧从gcc -nostdlib -no-pie获得静态可执行文件的原因-GCC不必做任何特殊的事情,只是ld进行了优化。

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

    细节

    使用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.31file --version文件-5.38-请注意,在最近的补丁程序中静态派检测已更改,而Ubuntu则选择了未发布的补丁程序。 (感谢@Joseph的侦探工作)-this in 2019检测到dynamic =具有PT_INTERP来处理静态饼,但基于PT_DYNAMIC却检测到reverted,因此共享库计为dynamicdebian bug #948269static-pie是一个不起眼的很少使用的功能。

    GCC最终以指定的动态链接器路径运行ld -pie exit.o,并且没有库。 (还有许多其他选项来支持可能的LTO链接时间优化,但是这里的键是-dynamic-linker /lib64/ld-linux-x86-64.so.2 -piecollect2只是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段以及ld.so的text/data/bss映射到ASLRed虚拟地址)。
    file和readelf更具描述性。

    来自gcc -nostdlib的PIE非静态可执行文件
    $ 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.)
    

    如果您跟踪它,还可以看到不同之处:
    $ 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(您也可以使用starti与GDB进行检查)。
    $ 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则由内核决定。

    令人惊讶的是,ldd并未说它不是动态可执行文件。 这可能是一个错误,或者是某些实现细节的副作用。
    gcc -nostdlib -static传统非PIE老式静态可执行文件
    $ 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输出的其他整个部分。

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

    两种情况下,ldd显然仅报告“不是动态可执行文件”:
  • no ELF解释器(动态链接器)路径
  • ELF类型= EXEC
  • 关于linux - Linux ldd上的 “statically linked”和 “not a dynamic executable”有什么区别?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/61553723/

    10-14 14:25