首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >来自Linux的“静态链接”和“非动态可执行文件”有什么区别?

来自Linux的“静态链接”和“非动态可执行文件”有什么区别?
EN

Stack Overflow用户
提问于 2020-05-02 02:27:48
回答 1查看 4.6K关注 0票数 13

考虑一下这个AMD64组装程序:

代码语言:javascript
运行
复制
.globl _start
_start:
    xorl %edi, %edi
    movl $60, %eax
    syscall

如果我用gcc -nostdlib编译并运行ldd a.out,就会得到以下内容:

代码语言:javascript
运行
复制
        statically linked

如果我用gcc -static -nostdlib编译并运行ldd a.out,就会得到以下结果:

代码语言:javascript
运行
复制
        not a dynamic executable

statically linkednot a dynamic executable有什么区别?如果我的二进制文件已经静态链接了,为什么添加-static会影响任何事情呢?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-05-02 05:37:37

这里有两件事:

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

filereadelf等其他工具提供了更多的信息,并使用了与您预期的相匹配的术语。

你的GCC是是默认的。,gcc不会为没有动态库的特殊情况做一个静态馅饼。

  • gcc -nostdlib只是做了一个饼,碰巧没有链接到任何库,但在其他方面与普通饼完全相同,指定了ELF解释器。 ldd 困惑地称此为“静态链接”. fileELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2 ...
  • gcc -nostdlib -static重写-pie默认值,并生成真正的静态可执行文件。 fileELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked ...
  • gcc -nostdlib -no-pie还选择将静态可执行文件作为对根本没有动态库的情况的优化。因为一个非饼型的可执行文件无论如何不可能是ASLRed,这是有道理的。与-static大小写相同的字节对字节。
  • gcc -nostdlib -static-pie生成一个不需要ELF解释器的ASLRable可执行文件。GCC在默认情况下不会为gcc -pie -nostdlib这样做,不像在不涉及动态链接库的情况下,它选择避开ld.sofileELF 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必须知道这意味着什么。与不需要显式请求动态可执行文件的非饼型情况不同,如果您传递ld任何.so库,您只会得到一个。我想这就是为什么你碰巧从gcc -nostdlib -no-pie得到了一个静态的可执行文件-- GCC不需要做什么特别的事情,只是ld在做那个优化。

但是ld在指定-pie时不隐式地启用-static,即使在没有共享库可链接的情况下也是如此。

详细信息

gcc --version gcc (ArchLinux9.3.0-1) 9.3.0生成的例子

ld --version GNU (GNU ) 2.34 (同时也是Binutils)

ldd --version ldd (GNU libc) 2.31

file --version文件-5.38-注意,静态饼检测在最近的补丁中发生了变化,Ubuntu选择了一个未发布的修补程序。(感谢@Joseph的检测工作)- 2019年检测到动态=有一个PT_INTERP来处理静态-饼,但是基于PT_DYNAMIC的是倒回来检测,所以共享库算作dynamicdebian bug #948269static-pie是一个鲜为人知的很少使用的特性。

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

代码语言:javascript
运行
复制
$ 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

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

file和readelf更具有描述性。

来自gcc -nostdlib的饼式非静态可执行文件

代码语言:javascript
运行
复制
$ 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.)

如果您将其捆绑在一起,您还可以看到其中的差异:

代码语言:javascript
运行
复制
$ 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 (您也可以使用starti与GDB检查)。

代码语言:javascript
运行
复制
$ strace ./exit-static-pie 
execve("./exit-static-pie", ["./exit-static-pie"], 0x7ffcdac96dd0 /* 51 vars */) = 0
exit(0)                                 = ?
+++ exited with 0 +++

gcc -nostdlib -static-pie

代码语言:javascript
运行
复制
$ 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 并没有说它不是一个动态的可执行文件。可能是一个bug,或者是一些实现细节的副作用。

gcc -nostdlib -static传统的非饼式旧式静态可执行文件

代码语言:javascript
运行
复制
$ 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 
   ...

这些都是程序头;与饼/静态饼不同的是,我不会遗漏任何部分,只会忽略readelf -a输出的其他部分。

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

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

  • 没有ELF解释器(动态链接器)路径
  • ELF型= EXEC
票数 14
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/61553723

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档