gcc-options

gcc 的编译选项

gcc官方手册:https://gcc.gnu.org/onlinedocs/

7.5手册:https://gcc.gnu.org/onlinedocs/gcc-7.5.0/gcc/#toc-GCC-Command-Options

链接选项:https://gcc.gnu.org/onlinedocs/gcc-7.5.0/gcc/Link-Options.html#Link-Options

-fPIC 和-fPIE

-fPIC 用在动态库的编译,产生位置无关的代码

-fPIE 用在可执行程序的编译

两者在效果上一样,都是把代码中的逻辑地址转为相对地址,但是作用上不一样。 -fPIC一般是动态库编译必须设置的,因为可能在链接时,多个模块之间的重定向可能会出现冲突。而-fPIE在可执行程序的编译中可加可不加,其将绝对地址转为相对地址,在一定程度上提高了安全性,另外相对寻址的方式可能会让程序在启动速度慢一点。

PIE 详解:https://www.redhat.com/en/blog/position-independent-executables-pie

-fPIC 在64 位系统中,动态库在链接编译静态库时也是需要加的,原因在于: 使用 32 位寄存器的汇编编译器无法访问64位平台地址偏移的范围,为了在使用64 位寄存器的情况下使用相同的代码,编译器需要借助一种方式进行标记,也就是使用-fPIC 或-mcmmodel=large 其实和直接编译动态库一样,只要涉及到动态库的编译加上 -fPIC 就行了,不管其底层是否依赖了静态库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// a.cc
#include <cstdio>
void func() {
printf("func\n");
}
// g++ -c a.cc -o a.o -fPIC
// ar rcs liba.a a.o

// b.cc
void func();
int func2() {
func();
}
// g++ -shared -o libb.so b.cc -L -l a

报错: /usr/bin/ld: /tmp/ccGoVApA.o: relocation R_X86_64_PC32 against symbol `_ZSt4cout@@GLIBCXX_3.4' can not be used when making a shared object; recompile with -fPIC /usr/bin/ld: final link failed: bad value collect2: error: ld returned 1 exit status

其实不引用 func(), 不依赖liba.a,在编译libb.so时也是需要加上 -fPIC 的

-L、-rpath和-rpath-link的区别

-L 选项: -L 选项用于指定链接器在链接程序时查找库文件的路径。 它告诉链接器在指定的路径中搜索库文件,以解析程序中的库依赖关系。 通常,您可以使用 -L 来指定非标准库目录,以便链接器能够找到您自己编译或第三方库的位置。 示例:-L/path/to/custom/libraries

-rpath 选项: -rpath 选项用于在可执行文件中嵌入运行时库的搜索路径。
它允许您指定在程序运行时查找共享库的路径,而不仅仅是在链接时。
运行时,程序将在指定的路径中搜索共享库,以满足依赖关系。
示例:-Wl,-rpath,/path/to/runtime/libs

-rpath-link 选项:
-rpath-link 选项类似于 -rpath,但它用于在链接时将库搜索路径嵌入到可执行文件中,而不是运行时。
这可以用来解决在链接时将某个库链接到了其他共享库版本而不是预期版本的问题。
-rpath-link 可以影响到链接过程中的库搜索路径,但不会影响运行时。
示例:-Wl,-rpath-link,/path/to/runtime/libs

总结:
-L 用于链接时,指定库文件的搜索路径。 -rpath 用于运行时,嵌入可执行文件中,指定共享库的搜索路径。 -rpath-link 用于链接时,指定库文件的搜索路径,但不会影响运行时。 这些选项通常用于处理库依赖关系,特别是当您有自定义库的位置或多个版本的库时,可以使用这些选项来确保链接器和运行时都能正确找到所需的库。

-L 和 -l 的顺序问题

-L 搜索路径时有顺序,按照从前往后;-l 搜索库时没有顺序

强制链接没有使用的库

-Wl,--no-as-needed https://sourceware.org/binutils/docs-2.16/ld/Options.html

只链接使用到的库(默认)

-Wl,--as-needed

-fstack-protector 栈溢出保护

编写一个简单的例子,发展栈增长的方向是从地址向高地址增长的,与理论上的增长方向相反:https://blog.csdn.net/qq_48974566/article/details/127076024

原因就在于开启了栈溢出保护选项。栈溢出保护的选项有好几个: https://www.cnblogs.com/arnoldlu/p/11630979.html 不同的选项的保护程度不一样,性能影响也不一样。

  • 栈溢出保护机制的基本实现原理: 通过 -fstack-protector 和 -fno-stack-protector 分别编译一个简单的程序,查看其区别就可以发现:
  1. 改变栈的使用方向,相当于预分配栈上的一大块空间,如果超过了预分配空间的写,就会改写其他内存数据
  2. 在栈分配空间前先标记一个位置,正常情况下该位置是不会被用户所改变和访问的,然后将该位置保存起来,等到分配和使用完栈空间后,再比较该位置的值是否发生了变化,从而判断栈是否发生了溢出。

分析示例:https://www.modb.pro/db/217464

也可以用 compiler explorer 进行比较

可以看到变量存储的方向是相反的。

发现只有分析示例里的 char a[64] 会用到标记位置的保护方式,int x[64]不行,可能是因为char* 的处理方式不太一样,也可能是上述理解的原理太简单,背后的实现更为复杂。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!