来自 Linux ldd 的 "statically linked" 和 "not a dynamic executable" 有什么区别?
What's the difference between "statically linked" and "not a dynamic executable" from Linux ldd?
考虑这个 AMD64 汇编程序:
.globl _start
_start:
xorl %edi, %edi
movl , %eax
syscall
如果我用 gcc -nostdlib
和 运行 ldd a.out
编译它,我得到这个:
statically linked
如果我用 gcc -static -nostdlib
和 运行 ldd a.out
编译它,我得到这个:
not a dynamic executable
statically linked
和 not a dynamic executable
有什么区别?如果我的二进制文件已经静态链接,为什么添加 -static
会影响任何东西?
这里有两个不同的东西:
- 是否请求 ELF 解释器 (ld.so)。
像 #!/bin/sh
但对于二进制文件,在 _start
.
之前运行
这是静态与动态 可执行文件 . 之间的区别
- 供 ld.so 加载的动态 linked 库列表恰好是空的。
这显然是 ldd
所说的 "statically linked",即您在构建时可能 link 编辑的任何库都是静态库。
file
和 readelf
等其他工具可提供更多信息并使用符合您预期的术语。
你的 GCC 是 configured so -pie
is the default,gcc 不会为没有动态库的特殊情况制作静态饼图。
gcc -nostdlib
只是制作了一个 PIE,它不会 link 到任何库,但在其他方面与普通 PIE 相同,指定了一个 ELF 解释器。
ldd
混淆地调用这个 "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
覆盖 -pie
默认值并生成真正的静态可执行文件。
file
: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked ...
gcc -nostdlib -no-pie
也选择制作静态可执行文件作为对根本没有动态库的情况的优化。由于非 PIE 可执行文件无论如何都不可能被 ASLRed,所以这是有道理的。逐字节与 -static
情况相同。
gcc -nostdlib -static-pie
生成不需要 ELF 解释器的 ASLRable 可执行文件。默认情况下,GCC 不会为 gcc -pie -nostdlib
执行此操作,这与在不涉及动态 linked 库时选择回避 ld.so
的 no-pie 情况不同。
file
: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), statically linked ...
-static-pie
晦涩难懂,很少使用,较旧的 file
不会将其识别为静态 linked。
-nostdlib
并不意味着 -no-pie
或 -static
,并且必须明确指定 -static-pie
才能得到它。
gcc -static-pie
调用 ld -static -pie
,所以 ld
必须知道这意味着什么。与您不必显式请求动态可执行文件的非 PIE 情况不同,如果您传递 ld
任何 .so
库,您只会得到一个。我认为这就是为什么您碰巧从 gcc -nostdlib -no-pie
获得静态可执行文件的原因 - GCC 不需要做任何特别的事情,它只是 ld
进行优化。
但是 ld
不会在指定 -pie
时隐式启用 -static
,即使 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 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
是一个鲜为人知的很少使用的功能。
GCC 最终 运行 ld -pie exit.o
指定了动态 linker 路径 ,并且没有库。 (还有大量其他选项支持可能的 LTO link 时间优化,但这里的关键是 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie
。collect2
只是 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 interpreter" /lib64/ld-linux-x86-64.so.2
在它上面运行,然后跳转到你的 _start
。 (虽然内核已经将可执行文件的ELF段映射到ASLRed虚拟地址,连同ld.so的text/data/bss)。
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
在 user-space 中执行的第一条指令是您的 _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
输出的其他整个部分。
还要注意程序头中的绝对虚拟地址,它不会让内核选择在虚拟地址 space 中映射文件的位置。这就是 ELF 对象的 EXEC 和 DYN 类型的区别。 PIE 可执行文件是具有入口点的共享对象,允许我们获得主可执行文件的 ASLR。实际的 EXEC 可执行文件具有 link-time-chosen 内存布局。
ldd
显然只在以下情况下报告 "not a dynamic executable":
- 没有 ELF 解释器(动态 linker)路径
- ELF类型=EXEC
考虑这个 AMD64 汇编程序:
.globl _start
_start:
xorl %edi, %edi
movl , %eax
syscall
如果我用 gcc -nostdlib
和 运行 ldd a.out
编译它,我得到这个:
statically linked
如果我用 gcc -static -nostdlib
和 运行 ldd a.out
编译它,我得到这个:
not a dynamic executable
statically linked
和 not a dynamic executable
有什么区别?如果我的二进制文件已经静态链接,为什么添加 -static
会影响任何东西?
这里有两个不同的东西:
- 是否请求 ELF 解释器 (ld.so)。
像#!/bin/sh
但对于二进制文件,在_start
.
之前运行 这是静态与动态 可执行文件 . 之间的区别
- 供 ld.so 加载的动态 linked 库列表恰好是空的。
这显然是ldd
所说的 "statically linked",即您在构建时可能 link 编辑的任何库都是静态库。
file
和 readelf
等其他工具可提供更多信息并使用符合您预期的术语。
你的 GCC 是 configured so -pie
is the default,gcc 不会为没有动态库的特殊情况制作静态饼图。
gcc -nostdlib
只是制作了一个 PIE,它不会 link 到任何库,但在其他方面与普通 PIE 相同,指定了一个 ELF 解释器。
ldd
混淆地调用这个 "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
覆盖-pie
默认值并生成真正的静态可执行文件。
file
:ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked ...
gcc -nostdlib -no-pie
也选择制作静态可执行文件作为对根本没有动态库的情况的优化。由于非 PIE 可执行文件无论如何都不可能被 ASLRed,所以这是有道理的。逐字节与-static
情况相同。gcc -nostdlib -static-pie
生成不需要 ELF 解释器的 ASLRable 可执行文件。默认情况下,GCC 不会为gcc -pie -nostdlib
执行此操作,这与在不涉及动态 linked 库时选择回避ld.so
的 no-pie 情况不同。
file
:ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), statically linked ...
-static-pie
晦涩难懂,很少使用,较旧的file
不会将其识别为静态 linked。
-nostdlib
并不意味着 -no-pie
或 -static
,并且必须明确指定 -static-pie
才能得到它。
gcc -static-pie
调用 ld -static -pie
,所以 ld
必须知道这意味着什么。与您不必显式请求动态可执行文件的非 PIE 情况不同,如果您传递 ld
任何 .so
库,您只会得到一个。我认为这就是为什么您碰巧从 gcc -nostdlib -no-pie
获得静态可执行文件的原因 - GCC 不需要做任何特别的事情,它只是 ld
进行优化。
但是 ld
不会在指定 -pie
时隐式启用 -static
,即使 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 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
是一个鲜为人知的很少使用的功能。
GCC 最终 运行 ld -pie exit.o
指定了动态 linker 路径 ,并且没有库。 (还有大量其他选项支持可能的 LTO link 时间优化,但这里的关键是 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie
。collect2
只是 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 interpreter" /lib64/ld-linux-x86-64.so.2
在它上面运行,然后跳转到你的 _start
。 (虽然内核已经将可执行文件的ELF段映射到ASLRed虚拟地址,连同ld.so的text/data/bss)。
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
在 user-space 中执行的第一条指令是您的 _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
输出的其他整个部分。
还要注意程序头中的绝对虚拟地址,它不会让内核选择在虚拟地址 space 中映射文件的位置。这就是 ELF 对象的 EXEC 和 DYN 类型的区别。 PIE 可执行文件是具有入口点的共享对象,允许我们获得主可执行文件的 ASLR。实际的 EXEC 可执行文件具有 link-time-chosen 内存布局。
ldd
显然只在以下情况下报告 "not a dynamic executable":
- 没有 ELF 解释器(动态 linker)路径
- ELF类型=EXEC