为什么 GCC 根据文件创建共享对象而不是可执行二进制文件?
Why does GCC create a shared object instead of an executable binary according to file?
我正在建设一个图书馆。当我 运行 以下任一项时,我的所有对象都会编译并 link 相继:
ar rcs lib/libryftts.a $^
gcc -shared $^ -o lib/libryftts.so
在我的 Makefile 中。我也能够成功地将它们安装到 /usr/local/lib
当我用 nm 测试文件时,所有的功能都在那里。
我的问题是,当我 运行 gcc testing/test.c -lryftts -o test && file ./test
或 gcc testing/test.c lib/libryftts.a -o test && file ./test
它说:
test: ELF 64-bit LSB shared object
而不是我期望的 test: ELF 64-bit LSB executable
。我做错了什么?
What am I doing wrong?
没有。
听起来您的 GCC 默认配置为构建 -pie
个二进制文件。这些二进制文件确实 是 共享库(类型 ET_DYN
),除了它们 运行 就像普通的可执行文件一样。
所以你应该 运行 你的二进制文件,并且(如果它有效)不用担心它。
或者您可以 link 带有 gcc -no-pie ...
的二进制文件,这应该会生成 ET_EXEC
类型的非 PIE
可执行文件,file
将说 ELF 64-bit LSB executable
.
file
5.36说的很清楚
file
5.36 实际上会清楚地显示可执行文件是否为 PIE,如所示:https://unix.stackexchange.com/questions/89211/how-to-test-whether-a-linux-binary-was-compiled-as-position-independent-code/435038#435038
例如,PIE 可执行文件显示为:
main.out: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, not stripped
和一个 non-PIE 一个:
main.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
该功能是在 5.33 中引入的,但它只是做了一个简单的 chmod +x
检查。在此之前,它只是为 PIE 打印 shared object
。
在 5.34 中,它原本打算开始检查更专业的 DF_1_PIE
ELF 元数据,但由于提交 9109a696f3289ba00eaa222fd432755ec4287e28 时的实现中存在错误,它实际上破坏了一些东西并将 GCC PIE 可执行文件显示为shared objects
.
该错误已在提交 03084b161cf888b5286dbbcd964c31ccad4f64d9 时在 5.36 中修复。
该错误特别存在于 Ubuntu 18.10 中,其中 file
5.34.
由于巧合ld -pie
链接汇编代码时没有出现
源代码分解显示在本答案的“file
5.36 源代码分析”部分。
Linux内核5.0根据ET_DYN
判断是否可以使用ASLR
file
"confusion" 的根本原因是 PIE executables 和共享库都是位置无关的,可以放置在随机的内存位置。
在fs/binfmt_elf.c内核只接受这两种类型的ELF文件:
/* First of all, some simple consistency checks */
if (interp_elf_ex->e_type != ET_EXEC &&
interp_elf_ex->e_type != ET_DYN)
goto out;
然后,只有 ET_DYN
才会将 load_bias
设置为非零值。然后 load_bias
决定了 ELF 偏移量:How is the address of the text section of a PIE executable determined in Linux?
/*
* If we are loading ET_EXEC or we have already performed
* the ET_DYN load_addr calculations, proceed normally.
*/
if (loc->elf_ex.e_type == ET_EXEC || load_addr_set) {
elf_flags |= elf_fixed;
} else if (loc->elf_ex.e_type == ET_DYN) {
/*
* This logic is run once for the first LOAD Program
* Header for ET_DYN binaries to calculate the
* randomization (load_bias) for all the LOAD
* Program Headers, and to calculate the entire
* size of the ELF mapping (total_size). (Note that
* load_addr_set is set to true later once the
* initial mapping is performed.)
*
* There are effectively two types of ET_DYN
* binaries: programs (i.e. PIE: ET_DYN with INTERP)
* and loaders (ET_DYN without INTERP, since they
* _are_ the ELF interpreter). The loaders must
* be loaded away from programs since the program
* may otherwise collide with the loader (especially
* for ET_EXEC which does not have a randomized
* position). For example to handle invocations of
* "./ld.so someprog" to test out a new version of
* the loader, the subsequent program that the
* loader loads must avoid the loader itself, so
* they cannot share the same load range. Sufficient
* room for the brk must be allocated with the
* loader as well, since brk must be available with
* the loader.
*
* Therefore, programs are loaded offset from
* ELF_ET_DYN_BASE and loaders are loaded into the
* independently randomized mmap region (0 load_bias
* without MAP_FIXED).
*/
if (elf_interpreter) {
load_bias = ELF_ET_DYN_BASE;
if (current->flags & PF_RANDOMIZE)
load_bias += arch_mmap_rnd();
elf_flags |= elf_fixed;
} else
load_bias = 0;
我通过实验证实了这一点:What is the -fPIE option for position-independent executables in gcc and ld?
file
5.36行为细分
从源头研究了 file
的工作原理。我们将得出结论:
- 如果
Elf32_Ehdr.e_type == ET_EXEC
- 打印
executable
- 否则如果
Elf32_Ehdr.e_type == ET_DYN
- if
DT_FLAGS_1
动态部分条目存在
- 如果
DF_1_PIE
设置在 DT_FLAGS_1
中:
- 打印
pie executable
- 其他
- 打印
shared object
- 其他
- 如果文件可以被用户、组或其他人执行
- 打印
pie executable
- 其他
- 打印
shared object
这里有一些实验证实:
Executable generation ELF type DT_FLAGS_1 DF_1_PIE chdmod +x file 5.36
--------------------------- -------- ---------- -------- -------------- --------------
gcc -fpie -pie ET_DYN y y y pie executable
gcc -fno-pie -no-pie ET_EXEC n n y executable
gcc -shared ET_DYN n n y pie executable
gcc -shared ET_DYN n n n shared object
ld ET_EXEC n n y executable
ld -pie --dynamic-linker ET_DYN y y y pie executable
ld -pie --no-dynamic-linker ET_DYN y y y pie executable
在 Ubuntu 18.10、GCC 8.2.0、Binutils 2.31.1 中测试。
每种类型实验的完整测试示例描述于:
gcc -pie
和 gcc -no-pie
:What is the -fPIE option for position-independent executables in gcc and ld?
请记住,自 Ubuntu 17.10 起,-pie
默认设置为开启,相关:32-bit absolute addresses no longer allowed in x86-64 Linux?
gcc -shared
(.so
共享库):https://github.com/cirosantilli/cpp-cheat/tree/b80ccb4a842db52d719a16d3716b02b684ebbf11/shared_library/basic
ld
实验:How to create a statically linked position independent executable ELF in Linux?
ELF type
和DF_1_PIE
分别由:
决定
readelf --file-header main.out | grep Type
readelf --dynamic main.out | grep FLAGS_1
file
5.36源码分析
要分析的关键文件是magic/Magdir/elf.
这种神奇的格式只根据固定位置的字节值来确定文件类型。
格式本身记录在:
man 5 magic
所以此时您需要准备好以下文件:
- http://www.sco.com/developers/devspecs/gabi41.pdf ELF header 部分的 ELF 标准
- http://www.cirosantilli.com/elf-hello-world/#elf-header我的ELF文件格式介绍与分解
在文件末尾,我们看到:
0 string 7ELF ELF
!:strength *2
>4 byte 0 invalid class
>4 byte 1 32-bit
>4 byte 2 64-bit
>5 byte 0 invalid byte order
>5 byte 1 LSB
>>0 use elf-le
>5 byte 2 MSB
>>0 use \^elf-le
7ELF
是每个 ELF 文件开头的 4 个魔法字节。 7
是 0x7F
的八进制。
然后通过与标准中的 Elf32_Ehdr
结构进行比较,我们看到字节 4(第 5 个字节,魔术标识符之后的第一个字节)确定了 ELF class:
e_ident[EI_CLASSELFCLASS]
它的一些可能值是:
ELFCLASS32 1
ELFCLASS64 2
然后在 file
源中,我们有:
1 32-bit
2 64-bit
和32-bit
和64-bit
是file
输出到标准输出的字符串!
所以现在我们在那个文件中搜索 shared object
,我们被引导到:
0 name elf-le
>16 leshort 0 no file type,
!:mime application/octet-stream
>16 leshort 1 relocatable,
!:mime application/x-object
>16 leshort 2 executable,
!:mime application/x-executable
>16 leshort 3 ${x?pie executable:shared object},
所以这个 elf-le
是包含在代码前面部分的某种标识符。
字节16正好是ELF类型:
Elf32_Ehdr.e_type
它的一些值是:
ET_EXEC 2
ET_DYN 3
因此,ET_EXEC
总是打印为 executable
。
ET_DYN
然而有两种可能性取决于 ${x
:
pie executable
shared object
${x
问:文件是否可以被用户、组或其他人执行?如果是,显示 pie executable
,否则显示 shared object
.
这个扩展是在src/softmagic.c
中的varexpand
函数中完成的:
static int
varexpand(struct magic_set *ms, char *buf, size_t len, const char *str)
{
[...]
case 'x':
if (ms->mode & 0111) {
ptr = t;
l = et - t;
} else {
ptr = e;
l = ee - e;
}
break;
然而,还有一个技巧!在 src/readelf.c
函数 dodynamic
中,如果存在动态部分 (PT_DYNAMIC
) 的 DT_FLAGS_1
标志条目,则 st->mode
中的权限将被存在覆盖或缺少 DF_1_PIE
标志:
case DT_FLAGS_1:
if (xdh_val & DF_1_PIE)
ms->mode |= 0111;
else
ms->mode &= ~0111;
break;
5.34的bug是初始代码写成:
if (xdh_val == DF_1_PIE)
这意味着如果设置了另一个标志,w由于 DF_1_NOW
,ich GCC 默认执行,可执行文件显示为 shared object
。
ELF 标准中没有描述 DT_FLAGS_1
flags 条目,因此它必须是 Binutils 扩展。
该标志在 Linux 内核 5.0 或 glibc 2.27 中没有用处,因此我似乎只是提供信息来指示文件是否为 PIE。
我正在建设一个图书馆。当我 运行 以下任一项时,我的所有对象都会编译并 link 相继:
ar rcs lib/libryftts.a $^
gcc -shared $^ -o lib/libryftts.so
在我的 Makefile 中。我也能够成功地将它们安装到 /usr/local/lib
当我用 nm 测试文件时,所有的功能都在那里。
我的问题是,当我 运行 gcc testing/test.c -lryftts -o test && file ./test
或 gcc testing/test.c lib/libryftts.a -o test && file ./test
它说:
test: ELF 64-bit LSB shared object
而不是我期望的 test: ELF 64-bit LSB executable
。我做错了什么?
What am I doing wrong?
没有。
听起来您的 GCC 默认配置为构建 -pie
个二进制文件。这些二进制文件确实 是 共享库(类型 ET_DYN
),除了它们 运行 就像普通的可执行文件一样。
所以你应该 运行 你的二进制文件,并且(如果它有效)不用担心它。
或者您可以 link 带有 gcc -no-pie ...
的二进制文件,这应该会生成 ET_EXEC
类型的非 PIE
可执行文件,file
将说 ELF 64-bit LSB executable
.
file
5.36说的很清楚
file
5.36 实际上会清楚地显示可执行文件是否为 PIE,如所示:https://unix.stackexchange.com/questions/89211/how-to-test-whether-a-linux-binary-was-compiled-as-position-independent-code/435038#435038
例如,PIE 可执行文件显示为:
main.out: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, not stripped
和一个 non-PIE 一个:
main.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
该功能是在 5.33 中引入的,但它只是做了一个简单的 chmod +x
检查。在此之前,它只是为 PIE 打印 shared object
。
在 5.34 中,它原本打算开始检查更专业的 DF_1_PIE
ELF 元数据,但由于提交 9109a696f3289ba00eaa222fd432755ec4287e28 时的实现中存在错误,它实际上破坏了一些东西并将 GCC PIE 可执行文件显示为shared objects
.
该错误已在提交 03084b161cf888b5286dbbcd964c31ccad4f64d9 时在 5.36 中修复。
该错误特别存在于 Ubuntu 18.10 中,其中 file
5.34.
由于巧合ld -pie
链接汇编代码时没有出现
源代码分解显示在本答案的“file
5.36 源代码分析”部分。
Linux内核5.0根据ET_DYN
file
"confusion" 的根本原因是 PIE executables 和共享库都是位置无关的,可以放置在随机的内存位置。
在fs/binfmt_elf.c内核只接受这两种类型的ELF文件:
/* First of all, some simple consistency checks */
if (interp_elf_ex->e_type != ET_EXEC &&
interp_elf_ex->e_type != ET_DYN)
goto out;
然后,只有 ET_DYN
才会将 load_bias
设置为非零值。然后 load_bias
决定了 ELF 偏移量:How is the address of the text section of a PIE executable determined in Linux?
/*
* If we are loading ET_EXEC or we have already performed
* the ET_DYN load_addr calculations, proceed normally.
*/
if (loc->elf_ex.e_type == ET_EXEC || load_addr_set) {
elf_flags |= elf_fixed;
} else if (loc->elf_ex.e_type == ET_DYN) {
/*
* This logic is run once for the first LOAD Program
* Header for ET_DYN binaries to calculate the
* randomization (load_bias) for all the LOAD
* Program Headers, and to calculate the entire
* size of the ELF mapping (total_size). (Note that
* load_addr_set is set to true later once the
* initial mapping is performed.)
*
* There are effectively two types of ET_DYN
* binaries: programs (i.e. PIE: ET_DYN with INTERP)
* and loaders (ET_DYN without INTERP, since they
* _are_ the ELF interpreter). The loaders must
* be loaded away from programs since the program
* may otherwise collide with the loader (especially
* for ET_EXEC which does not have a randomized
* position). For example to handle invocations of
* "./ld.so someprog" to test out a new version of
* the loader, the subsequent program that the
* loader loads must avoid the loader itself, so
* they cannot share the same load range. Sufficient
* room for the brk must be allocated with the
* loader as well, since brk must be available with
* the loader.
*
* Therefore, programs are loaded offset from
* ELF_ET_DYN_BASE and loaders are loaded into the
* independently randomized mmap region (0 load_bias
* without MAP_FIXED).
*/
if (elf_interpreter) {
load_bias = ELF_ET_DYN_BASE;
if (current->flags & PF_RANDOMIZE)
load_bias += arch_mmap_rnd();
elf_flags |= elf_fixed;
} else
load_bias = 0;
我通过实验证实了这一点:What is the -fPIE option for position-independent executables in gcc and ld?
file
5.36行为细分
从源头研究了 file
的工作原理。我们将得出结论:
- 如果
Elf32_Ehdr.e_type == ET_EXEC
- 打印
executable
- 打印
- 否则如果
Elf32_Ehdr.e_type == ET_DYN
- if
DT_FLAGS_1
动态部分条目存在- 如果
DF_1_PIE
设置在DT_FLAGS_1
中:- 打印
pie executable
- 打印
- 其他
- 打印
shared object
- 打印
- 如果
- 其他
- 如果文件可以被用户、组或其他人执行
- 打印
pie executable
- 打印
- 其他
- 打印
shared object
- 打印
- 如果文件可以被用户、组或其他人执行
- if
这里有一些实验证实:
Executable generation ELF type DT_FLAGS_1 DF_1_PIE chdmod +x file 5.36
--------------------------- -------- ---------- -------- -------------- --------------
gcc -fpie -pie ET_DYN y y y pie executable
gcc -fno-pie -no-pie ET_EXEC n n y executable
gcc -shared ET_DYN n n y pie executable
gcc -shared ET_DYN n n n shared object
ld ET_EXEC n n y executable
ld -pie --dynamic-linker ET_DYN y y y pie executable
ld -pie --no-dynamic-linker ET_DYN y y y pie executable
在 Ubuntu 18.10、GCC 8.2.0、Binutils 2.31.1 中测试。
每种类型实验的完整测试示例描述于:
gcc -pie
和gcc -no-pie
:What is the -fPIE option for position-independent executables in gcc and ld?请记住,自 Ubuntu 17.10 起,
-pie
默认设置为开启,相关:32-bit absolute addresses no longer allowed in x86-64 Linux?gcc -shared
(.so
共享库):https://github.com/cirosantilli/cpp-cheat/tree/b80ccb4a842db52d719a16d3716b02b684ebbf11/shared_library/basicld
实验:How to create a statically linked position independent executable ELF in Linux?
ELF type
和DF_1_PIE
分别由:
readelf --file-header main.out | grep Type
readelf --dynamic main.out | grep FLAGS_1
file
5.36源码分析
要分析的关键文件是magic/Magdir/elf.
这种神奇的格式只根据固定位置的字节值来确定文件类型。
格式本身记录在:
man 5 magic
所以此时您需要准备好以下文件:
- http://www.sco.com/developers/devspecs/gabi41.pdf ELF header 部分的 ELF 标准
- http://www.cirosantilli.com/elf-hello-world/#elf-header我的ELF文件格式介绍与分解
在文件末尾,我们看到:
0 string 7ELF ELF
!:strength *2
>4 byte 0 invalid class
>4 byte 1 32-bit
>4 byte 2 64-bit
>5 byte 0 invalid byte order
>5 byte 1 LSB
>>0 use elf-le
>5 byte 2 MSB
>>0 use \^elf-le
7ELF
是每个 ELF 文件开头的 4 个魔法字节。 7
是 0x7F
的八进制。
然后通过与标准中的 Elf32_Ehdr
结构进行比较,我们看到字节 4(第 5 个字节,魔术标识符之后的第一个字节)确定了 ELF class:
e_ident[EI_CLASSELFCLASS]
它的一些可能值是:
ELFCLASS32 1
ELFCLASS64 2
然后在 file
源中,我们有:
1 32-bit
2 64-bit
和32-bit
和64-bit
是file
输出到标准输出的字符串!
所以现在我们在那个文件中搜索 shared object
,我们被引导到:
0 name elf-le
>16 leshort 0 no file type,
!:mime application/octet-stream
>16 leshort 1 relocatable,
!:mime application/x-object
>16 leshort 2 executable,
!:mime application/x-executable
>16 leshort 3 ${x?pie executable:shared object},
所以这个 elf-le
是包含在代码前面部分的某种标识符。
字节16正好是ELF类型:
Elf32_Ehdr.e_type
它的一些值是:
ET_EXEC 2
ET_DYN 3
因此,ET_EXEC
总是打印为 executable
。
ET_DYN
然而有两种可能性取决于 ${x
:
pie executable
shared object
${x
问:文件是否可以被用户、组或其他人执行?如果是,显示 pie executable
,否则显示 shared object
.
这个扩展是在src/softmagic.c
中的varexpand
函数中完成的:
static int
varexpand(struct magic_set *ms, char *buf, size_t len, const char *str)
{
[...]
case 'x':
if (ms->mode & 0111) {
ptr = t;
l = et - t;
} else {
ptr = e;
l = ee - e;
}
break;
然而,还有一个技巧!在 src/readelf.c
函数 dodynamic
中,如果存在动态部分 (PT_DYNAMIC
) 的 DT_FLAGS_1
标志条目,则 st->mode
中的权限将被存在覆盖或缺少 DF_1_PIE
标志:
case DT_FLAGS_1:
if (xdh_val & DF_1_PIE)
ms->mode |= 0111;
else
ms->mode &= ~0111;
break;
5.34的bug是初始代码写成:
if (xdh_val == DF_1_PIE)
这意味着如果设置了另一个标志,w由于 DF_1_NOW
,ich GCC 默认执行,可执行文件显示为 shared object
。
ELF 标准中没有描述 DT_FLAGS_1
flags 条目,因此它必须是 Binutils 扩展。
该标志在 Linux 内核 5.0 或 glibc 2.27 中没有用处,因此我似乎只是提供信息来指示文件是否为 PIE。