Clang程序编译

环境变量使用方法

LIBRARY_PATH是编译时使用的
LD_LIBRARY_PATH是运行时使用的

编译过程

编译器的工作过程
配置configure,预处理,编译就是把代码转为汇编,汇编转为机器码。链接其他引用的代码。安装

常用命令

链接库与头文件命令生成

# 获取编译时需要链接的库或头文件
pkg-config --cflags openssl
-I/usr/local/include  

pkg-config --libs openssl
-L/usr/local/lib64 -lssl -lcrypto

gcc工具升级命令

出现报错

stdatomic.h: 没有那个文件或目录

解决方法,升级gcc版本

yum install centos-release-scl devtoolset-9
scl enable devtoolset-9 bash
gcc --version

obj文件转为动态链接库

gcc -shared a.o -o a.so

自定义软件编译后的安装目录

https://stackoverflow.com/questions/11307465/destdir-and-prefix-of-make

./configure --prefix=***

Number 1 determines where the package will go when it is installed, and where it will look for its associated files when it is run. It’s what you should use if you’re just compiling something for use on a single host.

make install DESTDIR=***

Number 2 is for installing to a temporary directory which is not where the package will be run from. For example this is used when building deb packages. The person building the package doesn’t actually install everything into its final place on his own system. He may have a different version installed already and not want to disturb it, or he may not even be root. So he uses

./configure --prefix=/usr
# so the program will expect to be installed in /usr when it runs, then

make install DESTDIR=debian/tmp
# to actually create the directory structure.

动态链接库关键的理解

Linux Dynamic Shared Library && LD Linker 从这篇文章吸取精华

优点

  1. 多个进程使用到同一个动态链接库文件,只要在内存中映射一份ELF .SO文件即可,有效地减少了进程的内存消耗
  2. 增加CPU缓存的命中率,因为不同进程间的数据和指令访问都集中在了同一个共享模块上
  3. 使程序的升级更加容易,在升级程序库或共享某个模块时,只要简单地将旧的目标文件覆盖掉,而无须将所有的程序再重新链接一遍。当程序下一次运行的时候,新版本的目标文件会被自动装载到内存并链接起来,程序就完成了升级的操作
  4. 程序可扩展性和兼容性
    使用动态链接技术,程序在运行时可以动态地选择加载各种程序模块,即插件技术(Plug-in)
    4.1 程序按照一定的规则制定好程序的接口,第三方开发者可以按照这种接口来编写符合要求的动态链接文件,该程序可以动态地载入各种由第三方开发的模块,在程序运行时动态地链接,实现程序功能的扩展。典型地如php的zend扩展、iis的filter/extension、apache的mod模块
    4.2 动态链接还可以加强程序的兼容性。一个程序在不同的平台运行时可以动态地链接到由操作系统提供的动态链接库,这些动态链接库在程序和操作系统之间增加了一个中间层,从而消除了程序对不同平台之间依赖的差异性
静态链接库的缺点
  1. 静态链接对内存和磁盘的浪费很严重,在静态链接中,C语言静态库是很典型的占用空间的例子
  2. 静态链接对程序的更新、部署、发布会造成严重的麻烦

地址无关代码: PIC

装载时重定位是解决动态模块中有绝对地址引用的方法之一,但是还存在一个问题,指令部分无法在多个进程间共享,为了解决这个问题,一个基本思想就是把指令中那些需要被修改的部分分离出来,跟数据部分放在一起,这样指令就可以保持不变,而数据部分可以在每个进程中拥有一个副本,这种方案就是地址无关代码(PIC Position-Independent Code)

  1. 可执行文件在编译时可以确定自己在进程虚拟地址空间中的位置,因为可执行文件往往都是第一个被加载的文件,它可以选择一个固定的位置
    1. Linux: 0x08040000
    2. Windows: 0x0040000
  2. 共享对象在编译时不能假设自己在进程虚拟地址空间中的位置
装载时重定位
  1. 链接时重定位(Link Time Relocation)
    -shared -fPIC
    在程序链接的时候就将代码中对绝对地址的引用重定位为实际的地址

  2. 装载时重定位(Load Time Relocation)
    -shared
    程序模块在编译时目标地址不确定,而需要在装载时将模块重定位

使用GCC产生地址无关代码很简单,只需要使用"-fPIC"参数即可。区分一个DSO是否为PIC的方法很简单,输入以下指令

readelf -d hook.so | grep TEXTREL
# PIC的DSO是不会包含任何代码段重定位表的,TEXTREL表示代码段重定位表地址

没有出现TEXTREL关键字

-fPIE

地址无关代码技术除了可以用在共享对象上面,它也可以用于可执行文件,一个以地址无关方式编译的可执行文件被称作地址无关可执行文件(PIE Position-Independent Executable),与GCC的"-fPIC"类似,产生PIE的参数为"-fPIE"

动态链接相关结构

在动态链接情况下,可执行文件的装载与静态链接的情况基本一样

  1. 操作系统读取可执行文件的头部,检查文件的合法性
  2. 从头部中的"Program Header"中读取每个"Segment"的虚拟地址、文件地址和属性,并将它们映射到进程虚拟空间的相对位置
  3. 在静态链接情况下,这个时候操作系统就可以把控制权交给可执行文件的入口地址,然后程序开始执行

但是在动态链接情况下,操作系统不能在装载完可执行文件之后就把控制权交给可执行文件,因为可执行文件依赖于很多动态共享对象(DSO),这个时候,可执行文件对于很多外部符号的引用还处于无效地址的状态,即还没有跟相应的共享对象中的实际位置链接起来,所以在映射完可执行文件之后,操作系统会先启动一个动态链接器(Dynamic Linker)

在Linux中,动态链接器ld.so实际上也是一个共享对象

  1. 操作系统同样通过映射的方式将它加载到进程的地址空间中
  2. 操作系统在加载完动态链接器之后,就将控制权交给动态链接器的入口地址(与可执行文件一样,共享对象也有入口地址)
  3. 当动态链接器得到控制权之后,它开始执行一系列自身的初始化操作,然后根据当前的环境参数,开始对可执行文件进行动态链接工作
  4. 当所有动态链接工作完成之后,动态链接器会将控制权转交到可执行文件的入口地址,程序开始正式执行

动态链接的步骤和实现

动态链接基本上分为3步

  1. 启动动态链接器本身(自举)
  2. 装载所有需要的共享对象
  3. 重定位、初始化
动态链接器自举

我们知道,对于Linux程序中的普通共享对象(DSO)文件来说

  1. 普通DSO的重定位工作由动态链接器来完成
  2. 普通DSO依赖的其他共享对象由动态链接器负责链接和装载

而对于动态链接器对应的DSO文件来说

  1. 动态链接器本身不可以依赖于其他任何共享对象。编写动态链接器时保证不使用任何系统库、运行库
  2. 动态链接器本身所需要的全局和静态变量的重定位工作由它本身完成
动态链接库就是执行文件

Linux动态链接器本身是一个共享对象,它的路径是"/lib/ld-linux.so.2、/lib64/ld-linux-x86-64.so.2"。共享对象本质上也是一个ELF文件,包含ELF文件头(包括e_entry、段表等),而动态链接器是个非常特殊的共享对象,它不仅是个共享对象,还是一个可执行程序,可以直接在命令行下运行

sudo /lib64/ld-linux-x86-64.so.2 --help

在这里插入图片描述

显式运行时链接

支持动态链接的系统大部分都支持一种更加灵活的模块加载方式,即"显式运行时链接(explicit run-time linking)(运行时加载)"。让程序自己在运行时控制加载指定的模块,并且可以在不需要该模块时将其卸载

在Linux中,从文件本身的格式上来看,动态库实际上和共享对象库没有区别,主要的区别是

  1. 共享对象是由动态链接器在程序启动之前负责装载和链接的,这一系列步骤都由动态链接器自动完成,对于程序本身是透明的
  2. 动态库的装载是通过一系列的动态链接器提供的API按成的

共享库的查找过程

我们知道,包括Linux系统在内的很多开源系统都是基于Glibc的,动态链接的ELF可执行文件在启动时同时会启动动态链接器(/lib/ld-linux.so.X),程序所依赖的共享对象全部由动态链接器负责装载和初始化,所以这里所谓的共享库的查找过程,本质上就是动态链接器(/lib/ld-linux.so.X)对共享库路径的搜索过程,搜索过程如下
复制代码

  1. 根据ELF文件中的配置信息
    任何一个动态链接的模块所依赖的模块路径保存在".dynamic"段中,由DT_NEED类型的项表示,动态链接器会按照这个路径去查找DT_RPATH所指定的路径,编译目标代码时,可以对gcc加入链接参数"-Wl,-rpath"指定动态库搜索路径
  2. DT_NEED段中保存的是绝对路径,则动态链接器直接按照这个路径进行直接加载
  3. 根据LD_PRELOAD中指定的路径加载共享库、目标文件
  4. /etc/ld.so.cache
    到了这一步,如果动态链接器(/lib/ld-linux.so.X)没有得到可以直接打开的绝对路径,则需要开始根据相对路径进行共享库的搜索
    Linux为了加速这个搜索过程,在系统中建立了一个ldconfig程序,这个程序负责
    1. 将共享库下的各个共享库维护一个SO-NAME(一一对应的符号链接),这样每个共享库的SO-NAME就能够指向正确的共享库文件
    2. 将全部SO-NAME收集起来,集中放到/etc/ld.so.cache文件里面,并建立一个SO-NAME的缓存
      当动态链接器要查找共享库时,它可以直接从/etc/ld.so.cache里面查找

所以,如果我们在系统指定的共享库目录下添加、删除或更新任何一个共享库,或者我们更改了/etc/ld.so.conf、/etc/ld.preload的配置,都应该运行一次ldconfig这个程序,以便更新SO-NAME和/etc/ld.so.cache
很多软件包的安装程序在结束共享库安装以后都会调用ldconfig

  1. 根据/etc/ld.so.preload中的配置进行搜索
    这个配置文件中保存了需要搜索的共享库路径,Linux动态共享库加载器根据顺序进行逐行广度搜索
  2. 根据环境变量LD_LIBRARY_PATH指定的动态库搜索路径
  3. DT_NEED段中保存的是相对路径,动态链接器会在按照一个约定的顺序进行库文件查找
    1. /lib
    2. /usr/lib
    3. 由/etc/ld.so.conf中配置指定的搜索路径
  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值