关于Linux下gcc 编译 C 源文件时,生成的是Shared object file而不是Executable file

最近在Debian下写C时,发现用readelf命令查看编译后的可执行文件类型时,发现文件类型是DYN (Shared object file),而不是EXEC (Executable file)。

-> % readelf -h a.out
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x580
  Start of program headers:          64 (bytes into file)
  Start of section headers:          6656 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         31
  Section header string table index: 30

多方查找,发现gcc默认加了--enable-default-pie选项(https://www.v2ex.com/amp/t/481562)

-> % gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/6/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 6.3.0-18+deb9u1' --with-bugurl=file:///usr/share/doc/gcc-6/README.Bugs --enable-languages=c,ada,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-6 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --enable-default-pie --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-6-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-6-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-6-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 6.3.0 20170516 (Debian 6.3.0-18+deb9u1)

关于gcc中pie选项可以参考这篇文章(https://blog.csdn.net/ivan240/article/details/5363395)

Position-Independent-Executable是Binutils,glibc和gcc的一个功能,能用来创建介于共享库和通常可执行代码之间的代码–能像共享库一样可重分配地址的程序,这种程序必须连接到Scrt1.o。标准的可执行程序需要固定的地址,并且只有被装载到这个地址时,程序才能正确执行。PIE能使程序像共享库一样在主存任何位置装载,这需要将程序编译成位置无关,并链接为ELF共享对象。

引入PIE的原因是让程序能装载在随机的地址,通常情况下,内核都在固定的地址运行,如果能改用位置无关,那攻击者就很难借助系统中的可执行码实施攻击了。类似缓冲区溢出之类的攻击将无法实施。而且这种安全提升的代价很小

因此,可以加上-no-pie 禁用掉该默认选项:

-> % readelf -h a.out
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400450
  Start of program headers:          64 (bytes into file)
  Start of section headers:          6480 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         30
  Section header string table index: 29

于是文件类型又变成了EXEC (Executable file)。

### Linux 环境下 GCC 使用指南 #### 安装 GCC 编译器 为了能够在 Linux 上使用 GCC 进行 C 或者 C++ 的编程工作,首先需要确认系统已经安装了 GCC 编译器。可以通过命令 `gcc --version` 来验证是否已成功安装[^5]。 如果未找到该指令,则表示尚未安装。此可以根据不同的发行版选择合适的包管理工具来进行安装操作。例如,在基于 Debian/Ubuntu 的系统上可以运行如下命令来获取最新版本的 GCC: ```bash sudo apt update && sudo apt install build-essential ``` 这不仅会安装 GCC 自身还会一并下载其他必要的构建工具链组件。 #### 创建简单的 C/C++ 源码文件 假设有一个非常基础的例子——经典的 "Hello, world!" 应用程序作为演示对象。创建一个新的文本文件命名为 `hello.c` 并输入下面的内容: ```c #include <stdio.h> int main() { printf("Hello, World!\n"); return 0; } ``` 保存此文件后即可准备进入下一步骤即编译过程[^3]。 #### 编译单个源代码文件 最简单的方式就是直接利用 gcc 命令加参数 `-o` 同指定输出的目标二进制名称。对于上述提到的那个例子来说完整的命令应该是这样的形式: ```bash gcc hello.c -o hello_program ``` 这里 `-o` 参数后面跟的是期望得到的结果文件的名字(`hello_program`)而不是默认产生的 `a.out`. 成功之后就可以通过 ./hello_program 来启动刚刚生成的应用程序查看效果了. #### 处理多个源文件项目 当面对更复杂的工程结构往往会有不止一个 .c/.cpp 文件参与进来构成整个应用程序的一部分。这就需要考虑如何有效地管理和链接各个模块之间的关系以便顺利完成最终产品的组装工作。一种常见的做法是借助于 make 工具配合 Makefile 描述符实现自动化流程控制[^1]: ```makefile CC=gcc CFLAGS=-Wall -g LDFLAGS= SOURCES=main.c module1.c module2.c OBJECTS=$(SOURCES:.c=.o) EXECUTABLE=myapp all: $(EXECUTABLE) $(EXECUTABLE): $(OBJECTS) $(CC) $(OBJECTS) -o $@ $(LDFLAGS) %.o: %.c $(CC) -c $< -o $@ $(CFLAGS) clean: rm -f *.o $(EXECUTABLE) ``` 这段脚本定义了一个小型项目的构建规则集,其中包含了目标依赖项解析逻辑以及具体的操作步骤说明等内容。 #### 动态库与静态库的区别及其制作方法 除了普通的可执行文件之外有候还需要打包一些共享资源供第三方调用这就是所谓的库的概念。根据加载机的不同又分为两种主要类别:动态链接库(DLL/SO) 和 静态链接库(A/LIB)[^4]. 针对前者而言其特点在于只会在实际运行期间才真正载入内存因此能够节省空间占用量;而后者则是在连接阶段就已经完全嵌入到了宿主进程中所以相对更加稳定可靠但是体积较大。以下是两个具体的实例展示怎样分别制造这两种类型的产物: ##### 构建静态库(.a): ```bash ar rcs libmystatic.a static.o ``` 这里的 ar 是 GNU 提供的一个档案处理实用程序专门用来创建和维护存档文件(也就是我们所说的静态库). 而后面的选项含义分别是: - r : 把新的 object file 加入到 archive 中; - c : 如果不存在就新建一个空的 archive; - s : 更新 symbol table. 最后面接的就是要形成的静态库名(libmystatic.a),紧接着是要加入的对象文件(static.o). ##### 制作动态库(.so): ```bash gcc -shared -o libmydynamic.so dynamic.o ``` 这条语句里的关键词解释如下: - shared :告诉编译器我们要建立一个共享库而非常规意义上的 exe. - o :指明即将诞生出来的 so 文件的具体路径连同全称一起给出. - 接下来的部分则是之前经过预处理、汇编等一系列工序所获得的目标文件列表(dynamic.o). #### 解决常见错误提示信息 在日常开发过程中难免遇到各种各样的报错情况,以下列举了几种比较典型的状况及对应的修复措施: - **找不到头文件**: 当尝试包含某个特定目录下的 header files 却总是失败的话可能是由于缺少相应的 include path 设置造成的。可以在 gcc/g++ 命令行里追加-I标志指向那个地方试试看。 - **无法识别函数声明**: 若发现某些 API 不被承认很可能是因为遗漏了外部符号导入环节所致。记得适引入正确的 library 支持哦! - **段错误(Segmentation Fault)**: 此类异常通常暗示着非法访问越界地址的行为发生过。建议启用调试模式(-g flag), 结合 GDB 工具逐步排查可疑区域直至定位根本原因为止。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值