load average 分别代表最近1分钟、5分钟、15分钟内系统的平均负荷。

可以使用 uptimewtop 命令查看。load average 的值越低,比如等于0.2或0.3,就说明电脑的工作量越小,系统负荷比较轻。

系统负荷的经验法则

  • 当系统负荷持续大于0.7,你必须开始调查了,问题出在哪里,防止情况恶化。
  • 当系统负荷持续大于1.0,你必须动手寻找解决办法,把这个值降下来。
  • 当系统负荷达到5.0,就表明你的系统有很严重的问题,长时间没有响应,或者接近死机了。
    阅读全文 »

转载:Linux 服务器的性能参数指标总结

  记录简单的工具查看系统的相关参数,当然很多工具也是通过分析加工 /proc、/sys 下的数据来工作的,而那些更加细致、专业的性能监测和调优,可能还需要更加专业的工具(perf、systemtap 等)和技术才能完成。毕竟来说,系统性能监控本身就是个大学问。

阅读全文 »

简介

  MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的 “轻量级”通讯协议,由IBM在1999年发布,它工作在TCP/IP协议族上,是为硬件性能低下的远程设备以及网络状况糟糕的情况下而设计的发布/订阅型消息协议。

  MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。

  目前应用比较广泛的是MQTT3.1.1,这个版本包括各种数据传输所需的功能和特征,而且对应生态非常成熟,因此以MQTT 3.1.1为例介绍一下MQTT的协议格式。

阅读全文 »

Linux find 命令用来在指定目录下查找文件。任何位于参数之前的字符串都将被视为欲查找的目录名。如果使用该命令时,不设置任何参数,则 find 命令将在当前目录下查找子目录与文件。并且将查找到的子目录和文件全部进行显示。

阅读全文 »

xargs(英文全拼: eXtended ARGuments)是给命令传递参数的一个过滤器,也是组合多个命令的一个工具。它能够捕获一个命令的输出,然后传递给另外一个命令。

xargs 可以将管道或标准输入(stdin)数据转换成命令行参数,也能够从文件的输出中读取数据。xargs 一般是和管道一起使用。

xargs 默认的命令是 echo,这意味着通过管道传递给 xargs 的输入将会包含换行和空白,不过通过 xargs 的处理,换行和空白将被空格取代。

阅读全文 »

箭头的指向就是数据的流向。

数字说明

  • 1、标准输入(英文:stdin): 代码为 0,使用 <<< 。数据流从右向左。
  • 2、标准正常输出(英文:stdout):代码为 1,使用 >>> 。数据流从左向右。
  • 3、标准错误输出(英文:stderr):代码为 2 ,使用 2>2>> 。数据流从左向右。
  • & :表示等同于的意思,如 &1
  • &>file:将标准输入和标准错误输出到重定向到文件。
    阅读全文 »

tail 命令和 head 命令正好相反,用来查看文件末尾的数据。

基本格式如下:tail [选项] 文件名

  • -n 行数 K:该选项表示输出最后 K 行,在此基础上,如果使用 -n +K,则表示从文件的第 K 行开始输出。
  • -c 行数 K: 该选项表示输出文件最后 K 个字节的内容,在此基础上,使用 -c +K 则表示从文件第 K 个字节开始输出。
  • -f :输出文件变化后新增加的数据。
    阅读全文 »

查看系统连接数

1
2
3
4
5
# 获取当前socket连接状态统计信息
cat /proc/net/sockstat

# 统计当前各种状态的连接的数量的命令
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

查看端口范围

1
2
3
# 允许系统打开的端口范围,用于向外链接的端口范围
cat /proc/sys/net/ipv4/ip_local_port_range
1024 65000
阅读全文 »

ss命令用来显示处于活动状态的套接字信息。ss命令可以用来获取socket统计信息,它可以显示和netstat类似的内容。但ss的优势在于它能够显示更多更详细的有关TCP和连接状态的信息,而且比netstat更快速更高效。ss命令是Linux CentOS 7中iproute软件包的一部分,默认已经安装。

当服务器的socket连接数量变得非常大时,无论是使用netstat命令还是直接cat /proc/net/tcp,执行速度都会很慢。

ss快的秘诀在于,它利用到了TCP协议栈中tcp_diag。tcp_diag是一个用于分析统计的模块,可以获得Linux 内核中第一手的信息,这就确保了ss的快捷高效。当然,如果你的系统中没有tcp_diag,ss也可以正常运行,只是效率会变得稍慢。

1
2
yum -y install iproute
yum info iproute

语法

ss(选项)

选项

  • -h:显示帮助信息;
  • -V:显示指令版本信息;
  • -n:不解析服务名称,以数字方式显示;
  • -a:显示所有的套接字;
  • -l:显示处于监听状态的套接字;
  • -o:显示计时器信息;
  • -m:显示套接字的内存使用情况;
  • -p:显示使用套接字的进程信息;
  • -i:显示内部的TCP信息;
  • -4:只显示ipv4的套接字;
  • -6:只显示ipv6的套接字;
  • -t:只显示tcp套接字;
  • -u:只显示udp套接字;
  • -d:只显示DCCP套接字;
  • -w:仅显示RAW套接字;
  • -x:仅显示UNIX域套接字。

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# 显示ICP连接
ss -t -a

# 显示 Sockets 摘要
ss -s

# 列出所有打开的网络连接端口
ss -l

# 查看进程使用的socket
ss -pl

# 显示所有UDP Sockets
ss -u -a

# 查看6379端口
ss -ta sport = :6379 | head
ss -pl | grep 3306

# 显示所有状态为established的SMTP连接
ss -o state established '( dport = :smtp or sport = :smtp )'

# 显示所有状态为Established的HTTP连接
ss -o state established '( dport = :http or sport = :http )'

# 列举出处于 FIN-WAIT-1状态的源端口为 80或者 443,目标网络为 193.233.7/24所有 tcp套接字
ss -o state fin-wait-1 '( sport = :http or sport = :https )' dst 193.233.7/24

# 用TCP 状态过滤Sockets
ss -4 state closing
ss -4 state FILTER-NAME
ss -6 state FILTER-NAME

# FILTER-NAME-HERE 可以代表以下任何一个:

established
syn-sent
syn-recv
fin-wait-1
fin-wait-2
time-wait
closed
close-wait
last-ack
listen
closing
all : 所有以上状态
connected : 除了listen and closed的所有状态
synchronized :所有已连接的状态除了syn-sent
bucket : 显示状态为maintained as minisockets,如:time-wait和syn-recv.
big : 和bucket相反.

# 匹配远程地址和端口号
ss dst ADDRESS_PATTERN
ss dst 192.168.1.5
ss dst 192.168.119.113:http
ss dst 192.168.119.113:smtp
ss dst 192.168.119.113:443

# 匹配本地地址和端口号
ss src ADDRESS_PATTERN
ss src 192.168.119.103
ss src 192.168.119.103:http
ss src 192.168.119.103:80
ss src 192.168.119.103:smtp
ss src 192.168.119.103:25

# 将本地或者远程端口和一个数比较
ss dport OP PORT
ss sport OP PORT

# ss dport OP PORT 远程端口和一个数比较;
# ss sport OP PORT 本地端口和一个数比较。
OP 可以代表以下任意一个:
<= or le : 小于或等于端口号
>= or ge : 大于或等于端口号
== or eq : 等于端口号
!= or ne : 不等于端口号
< or gt : 小于端口号
> or lt : 大于端口号

# ss 和 netstat 效率对比
time netstat -at
time ss

参考:

ss命令
每天一个linux命令(57):ss命令

Linux 三剑客,它们是 grep、awk、sed。

sed 全名叫 stream editor,流编辑器,用程序的方式来编辑文本,与 vim 的交互式编辑方式截然不同。它的功能十分强大,加上正则表达式的支持,可以进行大量的复杂文本的编辑操作。

阅读全文 »

Linux wc 命令用于计算字数。利用 wc 指令可以计算文件的Byte数、字数、或是列数,若不指定文件名称、或是所给予的文件名为”-“,则 wc 指令会从标准输入设备读取数据。

语法 wc [-clw][--help][--version][文件...]

参数:

  • -c或–bytes或–chars 只显示Bytes数。
  • -l或–lines 显示行数。
  • -w或–words 只显示字数。
  • –help 在线帮助。
  • –version 显示版本信息。

实例

在默认的情况下,wc将计算指定文件的 行数、字数,以及字节数。使用的命令为:

1
2
3
4
5
6
7
8
9
10
11
12
13
# testfile文件的统计信息
wc testfile
3 92 598 testfile # testfile文件的行数为3、单词数92、字节数598

#统计三个文件的信息
wc testfile testfile_1 testfile_2
3 92 598 testfile #第一个文件行数为3、单词数92、字节数598
9 18 78 testfile_1 #第二个文件的行数为9、单词数18、字节数78
3 6 32 testfile_2 #第三个文件的行数为3、单词数6、字节数32
15 116 708 总用量 #三个文件总共的行数为15、单词数116、字节数708

# 显示行数
wc -l

head 命令可用于查看文件的开头部分的内容,有一个常用的参数 -n 用于显示行数,默认为 10,即显示 10 行的内容。

命令格式:head [参数] [文件]

参数:

  • -q 隐藏文件名
  • -v 显示文件名
  • -c<数目> 显示的字节数。
  • -n<行数> 显示的行数。

实例:

1
head -n 15 1.log

Linux rlogin命令用于远端登入。

语法

rlogin [-8EL][-e <脱离字符>][-l <用户名称>][主机名称或IP地址]

必要参数:

  • -E 忽略escape字符
  • -8 只识别8位字的字符
  • -L 允许rlogin会话运行在litout模式
  • -ec 设置escape字符为c
  • -c 断开连接前要求确认
  • -a 强制要求远程主机在发送完一个空的本地用户名之后请求一个密码
  • -f 向远端主机发送一个本地认证
  • -F 向远程主机发送一个可转寄的本地认证
  • -7 强制执行7为的传输
  • -d 打开用于远端主机通信的TCP套接口的调试
  • -k 要求包含远端主机的tisckets
  • -x 启动数据传输的DES加密
  • -4 只使用 kerkberos的版本4的认证

选择参数:

  • -e<字符> 设置退出字符
  • -l<用户> 指定登陆的用户
  • -t<终端类型> 设置终端类型

实例

显示rlogin服务是否开启

1
chkconfig --list //检测rlogin服务是否开启

开启rlogin服务

1
chkconfig rlogin on //开启rlogin服务

登陆远程主机

1
2
3
4
rlogin 192.168.1.88
Password:
Password:
Login incorrect (不准确)

指定用户名登陆远程主机

1
2
3
4
rlogin 192.125.30.112 -l root

Passord:
Last login:Mon May 25 13:40:25 from 192.125.30.112

参考:

Linux rlogin命令

Linux ar命令用于建立或修改备存文件,或是从备存文件中抽取文件。

ar可让您集合许多文件,成为单一的备存文件。在备存文件中,所有成员文件皆保有原来的属性与权限。

语法

ar[-dmpqrtx][cfosSuvV][a<成员文件>][b<成员文件>][i<成员文件>][备存文件][成员文件]

参数:

必要参数:

  • -d  删除备存文件中的成员文件。
  • -m  变更成员文件在备存文件中的次序。
  • -p  显示备存文件中的成员文件内容。
  • -q  将文件附加在备存文件末端。
  • -r  将文件插入备存文件中。
  • -t  显示备存文件中所包含的文件。
  • -x  自备存文件中取出成员文件。

选项参数:

  • a<成员文件>  将文件插入备存文件中指定的成员文件之后。
  • b<成员文件>  将文件插入备存文件中指定的成员文件之前。
  • c  建立备存文件。
  • f  为避免过长的文件名不兼容于其他系统的ar指令指令,因此可利用此参数,截掉要放入备存文件中过长的成员文件名称。
  • i<成员文件>  将文件插入备存文件中指定的成员文件之前。
  • o  保留备存文件中文件的日期。
  • s  若备存文件中包含了对象模式,可利用此参数建立备存文件的符号表。
  • S  不产生符号表。
  • u  只将日期较新文件插入备存文件中。
  • v  程序执行时显示详细的信息。
  • V  显示版本信息。

实例

  • 打包文件
1
2
3
4
5
6
7
8
# ls   //显示当前目录文件   
a.c b.c d.c install.log qte
anaconda-ks.cfg c.c Desktop

# ar rv one.bak a.c b.c //打包 a.c b.c文件
ar: 正在创建 one.bak
a - a.c
a - b.c
  • 打包多个文件
1
2
3
4
5
6
# ar rv two.bak *.c  //打包以.c结尾的文件  
ar: 正在创建 two.bak
a - a.c
a - b.c
a - c.c
a - d.c
  • 显示打包文件的内容
1
2
3
4
5
# ar t two.bak    
a.c
b.c
c.c
d.c
  • 删除打包文件的成员文件
1
2
3
# ar d two.bak a.c b.c c.c  
# ar t two.bak
d.c

参考:

Linux ar命令

对象文件(Object files)分类

一,可重定位的对象文件(Relocatable file)

  由汇编器汇编生成的 .o 文件。后面的链接器(link editor)拿一个或一些 Relocatable object files 作为输入,经链接处理后,生成一个可执行的对象文件 (Executable file) 或者一个可被共享的对象文件(Shared object file)。可以使用 ar 工具将众多的 .o (Relocatable object files) 归档(archive)成 .a 静态库文件。内核可加载模块 .ko 文件也是 Relocatable object file。

二,可执行的对象文件(Executable file)

  文本编辑器vi、调式用的工具gdb、播放mp3歌曲的软件mplayer等等都是 Executable object file。在 Linux 系统里面,存在两种可执行的东西。除了 Executable object file,另外一种就是可执行的脚本(如shell脚本)。注意这些脚本不是 Executable object file,它们只是文本文件,但是执行这些脚本所用的解释器就是 Executable object file,比如 bash shell 程序。

三,可被共享的对象文件(Shared object file)

  可被共享的对象文件,即 动态库文件,也即 .so 文件。

动态库在发挥作用的过程中,必须经过两个步骤:

  • a) 链接编辑器(link editor)拿它和其他 Relocatable object file 以及其他 shared object file 作为输入,经链接处理后,生存另外的 shared object file 或者 executable file。
  • b) 在运行时,动态链接器(dynamic linker)拿它和一个 Executable file 以及另外一些 Shared object file 来一起处理,在 Linux 系统里面创建一个进程映像。

gcc翻译过程

20170611205306090.png

在Unix系统中,从源文件到可执行目标文件是由编译驱动程序完成的,如大名鼎鼎的gcc,翻译过程包括图中的是个阶段;

一,预处理阶段

预处理器(cpp)根据以字符#开头的命令修给原始的C程序,结果得到另一个C程序,通常以.i作为文件扩展名。主要是进行文本替换、宏展开、删除注释这类简单工作。

对应的命令:linux> gcc -E hello.c hello.i

二,编译阶段

编译器将文本文件hello.i翻译成hello.s,包含相应的汇编语言程序

对应的命令:linux> gcc -S hello.c hello.s

三,汇编阶段

将.s文件翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件.o中(把汇编语言翻译成机器语言的过程)。

把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。主要是进行词法分析和语法分析,又称为源程序分析,分析过程中发现有语法错误,给出提示信息。

对应的命令:linux> gcc -c hello.c hello.o

四,链接阶段

  此时hello程序调用了 printf 函数。 printf 函数存在于一个名为printf.o的单独的预编译目标文件中。 链接器(ld)就负责处理把这个文件并入到 hello.o 程序中,结果得到 hell.o 文件,一个可执行文件。最后可执行文件加载到储存器后由系统负责执行。

函数库一般分为静态库和动态库两种。

  • 静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为 .a
  • 动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为 .so,gcc 在编译时默认使用动态库。

ELF文件格式

  ELF 全称 Executable and Linkable Format,可执行可链接文件格式,目前常见的 Linux、 Android 可执行文件、共享库(.so)、目标文件(.o)以及Core 文件(吐核)均为此格式。

  ELF 文件由4部分组成,分别是 ELF 头(ELF header)、程序头表(Program header table)、节(Section)和节区头部表(Section header table)。ELF 头的位置是固定的,其余各部分的位置、大小等信息由ELF头中的各项值来决定。

20160521110158483.png

  • ELF header: 描述整个文件的组织。
  • Program Header Table: 描述文件中的各种 segments,用来告诉系统如何创建进程映像的。
  • sections 或者 segments:segments是从运行的角度来描述elf文件,sections是从链接的角度来描述elf文件,也就是说,在链接阶段,我们可以忽略program header table来处理此文件,在运行阶段可以忽略section header table来处理此程序(所以很多加固手段删除了section header table)。从图中我们也可以看出,segments与sections是包含的关系,一个segment包含若干个section。
  • Section Header Table: 包含了文件各个segction的属性信息。

  ELF 文件格式提供了两种视图,分别是链接视图和执行视图,链接视图是以节(section)为单位,执行视图是以段(segment)为单位。

  在汇编器和链接器看来,ELF 文件是由 Section Header Table 描述的一系列 Section 的集合,而执行一个 ELF 文件时,在加载器(Loader)看来它是由 Program Header Table 描述的一系列Segment的集合。

elf.png

程序头部表(Program Header Table),如果存在的话,告诉系统如何创建进程映像。
节区头部表(Section Header Table)包含了描述文件节区的信息,比如大小、偏移等。

执行命令 readelf -S android_server 来查看该可执行文件中有哪些section。

20160521110452230.png

行命令 readelf –segments android_server,可以查看该文件的执行视图。

20160521110508766.png

segment是section的一个集合,sections按照一定规则映射到segment。为什么需要区分两种不同视图?

  当ELF文件被加载到内存中后,系统会将多个具有相同权限(flg值)section合并一个segment。操作系统往往以页为基本单位来管理内存分配,一般页的大小为4096B,即4KB的大小。同时,内存的权限管理的粒度也是以页为单位,页内的内存是具有同样的权限等属性,并且操作系统对内存的管理往往追求高效和高利用率这样的目标。ELF文件在被映射时,是以系统的页长度为单位的,那么每个section在映射时的长度都是系统页长度的整数倍,如果section的长度不是其整数倍,则导致多余部分也将占用一个页。而我们从上面的例子中知道,一个ELF文件具有很多的section,那么会导致内存浪费严重。这样可以减少页面内部的碎片,节省了空间,显著提高内存利用率。

ELF Header

转载自:ELF文件格式解析

32位ELF文件中常用的数据格式:

20160521110646983.png

readelf -h android_server 命令,可以看到 ELF Header 结构的内容:

20160521110756954.png

对比以下三类ELF文件,我们得到了以下结论:
(1)e_type标识了文件类型
(2)Relocatable File(.o文件)不需要执行,因此e_entry字段为0,且没有Program Header Table等执行视图
(3)不同类型的ELF文件的Section也有较大区别,比如只有Relocatable File有.strtab节。

(1) Shared Object File(.so文件):
20160521110949018.png

(2) Executable File(可执行文件android_server):

20160521111008408.png

(3) Relocatable File(.o文件):

20160521111020956.png

在 ELF Header 中需要重点关注以下几个字段:

  • 1,e_entry:程序入口地址
    这个 sum.o 的进入点是 0x0(e_entry),这表面Relocatable objects不会有程序进入点。所谓程序进入点是指当程序真正执行起来的时候,其第一条要运行的指令的运行时地址。因为Relocatable objects file只是供再链接而已,所以它不存在进入点。而可执行文件test和动态库.so都存在所谓的进入点,且可执行文件的 e_entry 指向C库中的_start,而动态库.so中的进入点指向 call_gmon_start。
    如上图中 e_entry = 0xD8B0 (Executable File 文件),我们用ida打开该文件看到确实是 _start() 函数的地址。

20160521111227521.png

  • 2,e_ehsize:ELF Header结构大小
  • 3,e_phoff、e_phentsize、e_phnum:描述Program Header Table的偏移、大小、结构。
  • 4,e_shoff、e_shentsize、e_shnum:描述Section Header Table的偏移、大小、结构。
  • 5,e_shstrndx:这一项描述的是字符串表在 Section Header Table 中的索引,值25表示的是 Section Header Table 中第25项是字符串表(String Table)。

Section Header Table

  一个ELF文件中到底有哪些具体的 sections,由包含在这个ELF文件中的 section head table(SHT) 决定。在SHT中,针对每一个section,都设置有一个条目(entry),用来描述对应的这个section,其内容主要包括该 section 的名称、类型、大小以及在整个ELF文件中的字节偏移位置等等。我们也可以在TISCv1.2规范中找到SHT表中条目的C结构定义:

20160521111301410.png

解析 android_server 可执行ELF文件,我们可以看到 Section Header Table 中确实有23(17h (16进制表示))个条目,且索引为22(16h(16进制表示))确实为 section header section string table。

20160521111322394.png

打开条目,我们可以看到每个 entry 的具体字段,与上图的 Elf32_Shdr 结构一致。

20160521111341347.png

  需要注意的是,sh_name 值实际上是 .shstrtab 中的索引,该string table中存储着所有section的名字。下图中蓝色部分是.shstrtab的数据,我们可以看到,sh_name实际上是从索引1开始的”.shstrtab”字符串,因此这里的sh_name值为1h。

20160521111400597.png

Section

下面我们分析一些so文件中重要的Section,包括符号表、重定位表、GOT表等。

-符号表(.dynsym)

  符号表包含用来定位、重定位程序中符号定义和引用的信息,简单的理解就是符号表记录了该文件中的所有符号,所谓的符号就是经过修饰了的函数名或者变量名,不同的编译器有不同的修饰规则。例如符号_ZL15global_static_a,就是由global_static_a变量名经过修饰而来。

符号表项的格式如下:

1
2
3
4
5
6
7
8
9
10
typedef struct {  
Elf32_Word st_name; //符号表项名称。如果该值非0,则表示符号名的字
//符串表索引(offset),否则符号表项没有名称。
Elf32_Addr st_value; //符号的取值。依赖于具体的上下文,可能是一个绝对值、一个地址等等。
Elf32_Word st_size; //符号的尺寸大小。例如一个数据对象的大小是对象中包含的字节数。
unsigned char st_info; //符号的类型和绑定属性。
unsigned char st_other; //未定义。
Elf32_Half st_shndx; //每个符号表项都以和其他节区的关系的方式给出定义。
              //此成员给出相关的节区头部表索引。
} Elf32_sym;

通过 010Editor 解析出的符号表 .dynsym 的 section header 表项:

20160521111537942.png

符号表的具体内容:

20160521111608073.png

-字符串表(.dynstr)

字符串表中存放着所有符号的名称字符串。

字符串表的section header表项:

20160521111628840.png

再看一下下图中字符串表的具体内容,我们可以看出,.dynstr和.shstrtab 结构完全相同,不过一个存储的是符号名称的字符串,而另一个是Section 名称的字符串。

20160521111652559.png

-重定位表

  重定位表在ELF文件中扮演很重要的角色,首先我们得理解重定位的概念,程序从代码到可执行文件这个过程中,要经历编译器,汇编器和链接器对代码的处理。然而编译器和汇编器通常为每个文件创建程序地址从0开始的目标代码,但是几乎没有计算机会允许从地址0加载你的程序。如果一个程序是由多个子程序组成的,那么所有的子程序必需要加载到互不重叠的地址上。重定位就是为程序不同部分分配加载地址,调整程序中的数据和代码以反映所分配地址的过程。简单的言之,则是将程序中的各个部分映射到合理的地址上来。

  换句话来说,重定位是将符号引用与符号定义进行连接的过程。例如,当程序调用了一个函数时,相关的调用指令必须把控制传输到适当的目标执行地址。
具体来说,就是把符号的value进行重新定位。

可重定位文件必须包含如何修改其节区内容的信息,从而允许可执行文件和共享目标文件保存进程的程序映象的正确信息。这就是重定位表项做的工作。重定位表项的格式如下:

1
2
3
4
5
typedef struct {  
Elf32_Addr r_offset; //重定位动作所适用的位置(受影响的存储单位的第一个字节的偏移或者虚拟地址)
Elf32_Word r_info; //要进行重定位的符号表索引,以及将实施的重定位类型(哪些位需要修改,以及如何计算它们的取值)
//其中 .rel.dyn 重定位类型一般为R_386_GLOB_DAT和R_386_COPY;.rel.plt为R_386_JUMP_SLOT
} Elf32_Rel;
1
2
3
4
5
typedef struct {  
Elf32_Addr r_offset;
Elf32_Word r_info;
Elf32_Word r_addend;
} Elf32_Rela;

对 r_info 成员使用 ELF32_R_TYPE 宏运算可得到重定位类型,使用 ELF32_R_SYM 宏运算可得到符号在符号表里的索引值。 三种宏的具体定义如下:

1
2
3
#define ELF32_R_SYM(i) ((i)>>8) 
#define ELF32_R_TYPE(i) ((unsigned char)(i))
#define ELF32_R_INFO(s, t) (((s)

重定位表中的内容:

20160521111838248.png

以下是.rel.plt表的具体内容:

20160521111856928.png

我们可以看到,每8个字节(s_entsize)一个表项。第一个表项中的r_offset值为0xc7660,r_info为0xa16。其中r_offset指向下图中GOT表中第一项__imp_clock_gettime外部函数地址。那么我们如何利用r_offset值来找到其对应的符号呢?如上所述,进行 ELF32_R_SYM宏运算实际上就是将r_info右移8位,0xa16右移8位得到0xa,因此这就是其在符号表中的索引。

20160521111921663.png

从下图中可以看见符号表的s_entsize值为10h,即16个字节每条目。因此我们可以找到其索引为0xa的条目的st_name值为0x9ea。那么怎么证明我们确实找到的是clock_gettime函数的符号呢?我们再来看一下st_name值是不是正确的。

20160521111941390.png

st_name值表示的是符号名字符串中的第一个字符在字符串表中的偏移量,因此我们用0x9ea加上符号表的起始位置(0x7548)就能得到该字符串在‭0x7F32位置。如下图所示:

20160521112219618.png

-常见的重定位表类型:

  • .rel.text:重定位的地方在.text段内,以offset指定具体要定位位置。在链接时候由链接器完成。.rel.text属于普通重定位辅助段 ,他由编译器编译产生,存在于obj文件内。连接器连接时,他用于最终可执行文件或者动态库的重定位。通过它修改原obj文件的.text段后,合并到最终可执行文件或者动态文件的.text段。其类型一般为R_386_32和R_386_PC32。

  • .rel.dyn:重定位的地方在.got段内。主要是针对外部数据变量符号。例如全局数据。重定位在程序运行时定位,一般是在.init段内。定位过程:获得符号对应value后,根据rel.dyn表中对应的offset,修改.got表对应位置的value。另外,.rel.dyn 含义是指和dyn有关,一般是指在程序运行时候,动态加载。区别于rel.plt,rel.plt是指和plt相关,具体是指在某个函数被调用时候加载。我个人理解这个Section的作用是,在重定位过程中,动态链接器根据r_offset找到.got对应表项,来完成对.got表项值的修改。

  • .rel.dyn和.rel.plt是动态定位辅助段。由连接器产生,存在于可执行文件或者动态库文件内。借助这两个辅助段可以动态修改对应.got和.got.plt段,从而实现运行时重定位。

  • .rel.plt:重定位的地方在.got.plt段内(注意也是.got内,具体区分而已)。 主要是针对外部函数符号。一般是函数首次被调用时候重定位。首次调用时会重定位函数地址,把最终函数地址放到.got内,以后读取该.got就直接得到最终函数地址。我个人理解这个Section的作用是,在重定位过程中,动态链接器根据r_offset找到.got对应表项,来完成对.got表项值的修改。

  • .plt段(过程链接表):所有外部函数调用都是经过一个对应桩函数,这些桩函数都在.plt段内。具体调用外部函数过程是:
    调用对应桩函数—>桩函数取出.got表表内地址—>然后跳转到这个地址.如果是第一次,这个跳转地址默认是桩函数本身跳转处地址的下一个指令地址(目的是通过桩函数统一集中取地址和加载地址),后续接着把对应函数的真实地址加载进来放到.got表对应处,同时跳转执行该地址指令.以后桩函数从.got取得地址都是真实函数地址了。
    下图是.plt某表项,它包含了取.got表地址和跳转执行两条指令。

20160521112646041.png

  • .got(全局偏移表)

Program Header Table

  程序头部(Program Header)描述与程序执行直接相关的目标文件结构信息。用来在文件中定位各个段的映像。同时包含其他一些用来为程序创建映像所必须的信息。
可执行文件或者共享目标文件的程序头部是一个结构数组,每个结构描述了一个段或者系统准备程序执行所必须的其他信息。目标文件的 “段” 包含一个或者多个 “节区”,也就是 “段内容(Segment Contents)”。程序头部仅对可执行文件和共享目标文件有意义。

程序头部的数据结构如下:

1
2
3
4
5
6
7
8
9
10
typedef struct {  
Elf32_Word p_type; //此数组元素描述的段的类型,或者如何解释此数组元素的信息。
Elf32_Off p_offset; //此成员给出从文件头到该段第一个字节的偏移
Elf32_Addr p_vaddr; //此成员给出段的第一个字节将被放到内存中的虚拟地址
Elf32_Addr p_paddr; //此成员仅用于与物理地址相关的系统中。System V忽略所有应用程序的物理地址信息。
Elf32_Word p_filesz; //此成员给出段在文件映像中所占的字节数。可以为0。
Elf32_Word p_memsz; //此成员给出段在内存映像中占用的字节数。可以为0。
Elf32_Word p_flags; //此成员给出与段相关的标志。
Elf32_Word p_align; //此成员给出段在文件中和内存中如何对齐。
} Elf32_phdr;

我们看到,以下两个工具确实是照此格式解析的:

20160521112705213.png

20160521112720713.png

参考:

ELF文件格式解析

ELF文件详解—初步认识

ELF 格式详解(一)

ELF文件格式解析

Linux进程地址空间学习(二)