为什么GCC根据文件创建共享对象而不是可执行二进制文件?

时间:2015-12-29 21:41:33

标签: c gcc shared-libraries static-libraries

我有一个正在建设的图书馆。当我运行以下任何一个时,我的所有对象都会连续编译和链接: ar rcs lib/libryftts.a $^

gcc -shared $^ -o lib/libryftts.so

在我的Makefile中。我也能够成功地将它们安装到/usr/local/lib 当我用nm测试文件时,所有函数都在那里。 我的问题是当我运行gcc testing/test.c -lryftts -o test && file ./testgcc testing/test.c lib/libryftts.a -o test && file ./test时 它说:

正如我所料,

test: ELF 64-bit LSB shared object而不是test: ELF 64-bit LSB executable。我做错了什么?

2 个答案:

答案 0 :(得分:27)

  

我做错了什么?

没有

听起来您的GCC默认配置为构建-pie二进制文件。这些二进制文件确实共享库(类型为ET_DYN),除非它们像普通的可执行文件一样运行。

所以你应该运行你的二进制文件,并且(如果它有效)不要担心它。

或者您可以将二进制文件与gcc -no-pie ...相关联,这样就可以生成类型为PIE的非ET_EXEC可执行文件,file将为其ELF 64-bit LSB executable

答案 1 :(得分:3)

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

和一个非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可执行文件显示为{{ 1}}。

该错误已在5.36版中的提交03084b161cf888b5286dbbcd964c31ccad4f64d9中修复。

该错误特别存在于具有shared objects 5.34的Ubuntu 18.10中。

由于巧合,在将汇编代码与file链接时,它不会显现出来。

此答案的“ ld -pie 5.36源代码分析”部分显示了源代码细分。

Linux内核5.0根据file

确定是否可以使用ASLR。

ET_DYN“混乱”的根本原因在于,PIE executables和共享库都是位置无关的,可以放置在随机存储位置。

fs/binfmt_elf.c,内核仅接受这两种类型的ELF文件:

file

然后,仅将/* First of all, some simple consistency checks */ if (interp_elf_ex->e_type != ET_EXEC && interp_elf_ex->e_type != ET_DYN) goto out; 的{​​{1}}设置为非零值。然后ET_DYN决定ELF偏移量:How is the address of the text section of a PIE executable determined in Linux?

load_bias

我在What is the -fPIE option for position-independent executables in gcc and ld?

上通过实验进行了确认

load_bias 5.36行为细分

在研究/* * 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; 的来源之后。我们将得出以下结论:

  • 如果file
    • 打印file
  • 否则为Elf32_Ehdr.e_type == ET_EXEC
    • 如果存在executable个动态部分,
      • 如果在Elf32_Ehdr.e_type == ET_DYN中设置了DT_FLAGS_1
        • 打印DF_1_PIE
      • 其他
        • 打印DT_FLAGS_1
    • 其他
      • 文件是否可以由用户,组或其他人执行
        • 打印pie executable
      • 其他
        • 打印shared object

这是一些证实这一点的实验:

pie executable

在Ubuntu 18.10,GCC 8.2.0,Binutils 2.31.1中进行了测试。

每种实验的完整测试示例在以下网址中描述:

.sold分别通过以下方式确定:

ELF type

DF_1_PIE 5.36源代码分析

要分析的密钥文件是magic/Magdir/elf

这种魔术格式仅取决于固定位置的字节值来确定文件类型。

该格式本身记录在:

readelf --file-header main.out | grep Type
readelf --dynamic     main.out | grep FLAGS_1

因此,在这一点上,您将需要准备以下文件:

在文件末尾,我们看到:

file

man 5 magic 是每个ELF文件开头的4个魔术字节。 0 string \177ELF 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 \177ELF的八进制。

然后通过与标准中的\177结构进行比较,我们看到字节4(第5个字节,魔术标识符之后的第一个字节)确定了ELF类:

0x7F

及其一些可能的值是:

Elf32_Ehdr

那么在e_ident[EI_CLASSELFCLASS] 源中,我们有:

ELFCLASS32 1
ELFCLASS64 2

file1 32-bit 2 64-bit 32-bit输出到标准输出的字符串!

因此,现在我们在该文件中搜索64-bit,并被引至:

file

因此,shared object是某种标识符,已包含在代码的上半部分。

字节16正是ELF类型:

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

因此,Elf32_Ehdr.e_type 始终被打印为ET_EXEC 2 ET_DYN 3

ET_EXEC但是有两种可能性取决于executable

  • ET_DYN
  • ${x

pie executable问:文件是否可以由用户,组或其他人执行?如果是,则显示shared object,否则显示${x

此扩展是在src/softmagic.c中的pie executable函数中完成的:

shared object

但是,还有另外一种技巧!在src/readelf.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; )的dodynamic标志条目,则DT_FLAGS_1中的权限将被存在覆盖或没有PT_DYNAMIC标志:

st->mode

5.34中的错误是初始代码编写为:

DF_1_PIE

这意味着如果设置了另一个标志(默认情况下,由于case DT_FLAGS_1: if (xdh_val & DF_1_PIE) ms->mode |= 0111; else ms->mode &= ~0111; break; 而GCC进行了设置),则可执行文件显示为 if (xdh_val == DF_1_PIE)

DF_1_NOW标志条目在ELF标准中没有描述,因此它必须是Binutils扩展名。

该标志在Linux内核5.0或glibc 2.27中没有用,所以我似乎纯粹是在提供信息,以指示文件是否为PIE。