编译问题
1.关于在项目中使用gcc时遇到的一些问题:
注:星号表示cgo相关
1*.cgo使用的编译器是gcc/g++(需要另外自己单独安装)。
2.gcc头文件搜索的方式有三种:
a.参数 -I 指定搜索目录,或者在包含头文件的文件的同级目录。
b.设置环境变量C_INCLUDE_PATH 和CPLUS_INCLUDE_PATH。
c.内定的路径,查看方式,另写一个小测试案例,使用gcc test.c -v ,其中一行输出内容是:#include <…> search starts here:…可查看gcc的默认查找目录。
3*.cgo中参数的设置,CFLAGS处理.c文件头文件搜索,CXXFLAGS处理.cpp文件头文件搜索,LDFLAGS库路径搜索,一般使用绝对路径,也可以使用$(SRCDIR)–当前相对路径。
4.对于PATH环境变量的理解:exe(PATH)执行文件会在环境变量中搜寻
5*.go build会生成缓存文件,如果需要编译的文件不改变则不会重新编译,-a选项可以强制重新编译。
6.库的链接默认搜索路径查看:ld --verbose | grep SEARCH;
7.库链接时进行的链接操作过程查看:gcc test.c -Wl,–verbose;
8.动态库的搜索路径LD_LIBRARY_PATH;
2 c/c++条件编译
介绍c语言中条件编译相关的预编译指令,包括 #define、#undef、#ifdef、#ifndef、#if、#elif、#else、#endif、defined。
#define 定义一个预处理宏
#undef 取消宏的定义
#ifdef 判断某个宏是否被定义,若已定义,执行随后的语句
#ifndef 与#ifdef相反,判断某个宏是否未被定义
#if 编译预处理中的条件命令,相当于C语法中的if语句
#elif 若#if, #ifdef, #ifndef或前面的#elif条件不满足,则执行#elif之后的语句,相当于C语法中的else-if
#else 与#if, #ifdef, #ifndef对应, 若这些条件不满足,则执行#else之后的语句,相当于C语法中的else
#endif #if, #ifdef, #ifndef这些条件命令的结束标志.
defined 与#if, #elif配合使用,判断某个宏是否被定义
#ifdef 和 #if defined 两者的区别,前者的常用法是:
#ifdef XXX
#else
#endif
在两者中选择是否有定义(当然也可以如2.2中简化使用)。对于后者,常用法是:
#if defined xxx1
#elif defined xxx2
#elif defined xxx3
#endif
可以在多个中选择是否有定义.
2.2 不同平台编译宏
#ifdef _WIN32
//define something for Windows (32-bit and 64-bit, this part is common)
#ifdef _WIN64
//define something for Windows (64-bit only)
#else
//define something for Windows (32-bit only)
#endif
#elif __APPLE__
#include "TargetConditionals.h"
#if TARGET_IPHONE_SIMULATOR
// iOS Simulator
#elif TARGET_OS_IPHONE
// iOS device
#elif TARGET_OS_MAC
// Other kinds of Mac OS
#else
# error "Unknown Apple platform"
#endif
#elif __ANDROID__
// android
#elif __linux__
// linux
#elif __unix__ // all unices not caught above
// Unix
#elif defined(_POSIX_VERSION)
// POSIX
#else
# error "Unknown compiler"
#endif
3.gcc编译参数fpic和fpie
Position-Independent-Executable是Binutils,glibc和gcc的一个功能, 能用来创建介于共享库和通常可执行代码之间的代码–能像共享库一样可重分配地址的程序, 这种程序必须连接到Scrt1.o。标准的可执行程序需要固定的地址,并且只有被装载到这个地址时, 程序才能正确执行。PIE能使程序像共享库一样在主存任何位置装载,这需要将程序编译成位置无关, 并链接为ELF共享对象。
引入PIE的原因是让程序能装载在随机的地址,通常情况下,内核都在固定的地址运行,如果能改用位置无关, 那攻击者就很难借助系统中的可执行码实施攻击了。类似缓冲区溢出之类的攻击将无法实施。而且这种安全提升的代价很小
谈到PIE就不得不说说Pax和Grsec内核。这两个东西都是为了构建坚不可摧到安全系统而准备的。 PaX是Linux内核安全增强补丁,它能在两方面保证安全性,一是ASLR(Address Space Layout Randomization, 地址空间分布随机化),这是一种将所有数据装载到内存时都随机化地址的方式,当使用PIE选项编译应用时, PaX能将应用的地址做随机加法;二是能提供不可执行的内存空间,这样就能使得攻击者放入内存中的 恶意代码不可执行。不过PaX官网上能支持的最新内核是2.6.27,已经是一年前的更新了。Grsec也时类似的内核补丁, 更新较为频繁能支持最新的2.6.32内核。这两种方式都能将内核完全位置无关,除了Grub和Glibc中无法位置无关的汇编码。
PIE最早由RedHat的人实现,他在连接起上增加了-pie选项,这样使用-fPIE编译的对象就能通过连接器 得到位置无关可执行程序。fPIE和fPIC有些不同。可以参考Gcc和Open64中的-fPIC选项.
gcc中的-fpic选项,使用于在目标机支持时,编译共享库时使用。编译出的代码将通过全局偏移表 (Global Offset Table)中的常数地址访存,动态装载器将在程序开始执行时解析GOT表项(注意, 动态装载器操作系统的一部分,连接器是GCC的一部分).而gcc中的-fPIC选项则是针对某些特殊机型做了 特殊处理,比如适合动态链接并能避免超出GOT大小限制之类的错误。而Open64仅仅支持不会导致GOT表溢出的PIC编译。 gcc中的-fpie和-fPIE选项和fpic及fPIC很相似,但不同的是,除了生成为位置无关代码外, 还能假定代码是属于本程序。通常这些选项会和GCC链接时的-pie选项一起使用。fPIE选项仅能在编译可执行码时用, 不能用于编译库。所以,如果想要PIE的程序,需要你除了在gcc增加-fPIE选项外,还需要在ld时增加-pie选项 才能产生这种代码。即gcc -fpie -pie来编译程序。单独使用哪一个都无法达到效果。
3.1 GCC的W参数
-Wa,< option > 该选项传递参数给汇编器(option是汇编器使用的参数)
-Wp,< option > 该选项传递参数给预处理器
-Wl,< option > 该选项传递参数给链接器(更多关于option的值可以查阅ld的参数)
e.g :
gcc -Wl,-Bstatic -lxx -Wl,-Bdynamic -lxxx -Wl,-Map,xxx.map(GNU ld可以使用-Wl,-Map=xxx.map)
该编译选项告诉链接器xx库使用静态链接,xxx库使用动态链接,并生成map文件
使用GCC 的-l参数指定库时可以使用 -l :filename,即可以指定库的全名,而不是去除lib和.a的短名称
4.linux下动态库路径设置
第一种:将库拷贝到/usr/lib和/lib下。
sudo cp libmyhello.so /usr/lib
sudo cp libmyhello.so /lib
此时再执行./hello即可得到正确的显示结果。
第二种:修改LD_LIBRARY_PATH环境变量:
sudo vim /etc/bashrc 对所有用户生效 或者修改 ~/.bashrc 这样就只对当前用户生效,
在文件最后,添加:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/linux/file/dongtaiku
保存退出,重启终端,此时再执行./hello即可得到正确的显示结果。
第三种:添加/etc/ld.so.conf.d/XXX.conf文件,或者直接在/etc/ld.so.conf文件中添加动态库目录,
vim /etc/ld.so.conf.d/XXX.conf或vim /etc/ld.so.conf,
在文件内添加动态库的目录,
/home/linux/file/dongtaiku,
保存退出,执行ldconfig刷新缓存,生效,
sudo ldconfig
注:使用ldconfig -p可以打印当前缓存(缓存保存在/etc/ld.so.cache中)
第四种:编译时使用Wl,rpath,xxx指定运行时的库路径
5. 动态库的显示加载和隐式加载
动态库在链接到程序中时,可以有两种不同的方式(linux):
- 隐示加载,编译时需要链接具体的库名和头文件;使用过程中,直接调用动态库api即可。
- 显示加载,通过类dlopen函数来使用动态库,链接时无需具体的库名和头文件,但是需要链接相关库
-ldl
6.gcc常用参数
-lrt 一些实时库,例如,shm_open
-dl 可以显式加载动态库的动态函数库
-rdynamic 指示连接器把所有符号都在加载到符号表中,以便那些通过 dlopen() 或 backtrace() (这一系列函数使用.dynsym表内符号)这样的函数使用。
注意:在使用gcc时,一般将被依赖的库放在依赖的后面,例如A依赖B,则gcc -A -B,参数的解析是从右往左
7.关于系统库
libc(ANSI C)和glibc(GNU C)都是属于c语言标准库,目前大多数使用glibc;libc++和libstdc++属于c++标准库
链接方式:最理想的标准库链接方式就是半静态链接,通常会选择将 libgcc 与 libstdc++ 这两个标准库静态链接(libc也会存在版本兼容问题,静态链接就是为了解决版本兼容问题,高版本的系统库编译的程序不能在低版本的系统库环境下运行)
-static-libgcc:静态链接 gcc 库
-static-libstdc++:静态链接 c++ 库