导图社区 编译和链接
这是一篇关于编译和链接的思维导图,内核编译和安装,工具链gcc,id,objdump,gdp内容点总结。
编辑于2022-12-06 13:39:54 浙江省这是一篇关于程序和库信息的思维导图,主要内容包括:(查看基础信息),获取ELF节的长度信息,显示可执行文件或库需要静态加载的动态库完整列表--显示加载时的依赖项,列出二进制文件的节信息,查看动态节,列出并查看段,查看重定位节,反汇编,列出库中未定义的符号,列出动态符号,列出二进制文件或库的符号表,查看节中的数据,符号的类型。
这是一篇关于设备驱动的思维导图,主要内容包括:主要功能,设备驱动模型。阐述了设备驱动的主要功能、信号定义、设备驱动模型等内容。
这是一篇关于算法的思维导图,主要内容包括:云计算,内存管理算法,分布式同步算法,避免死锁算法,进程调度算法,磁盘调度算法。
社区模板帮助中心,点此进入>>
这是一篇关于程序和库信息的思维导图,主要内容包括:(查看基础信息),获取ELF节的长度信息,显示可执行文件或库需要静态加载的动态库完整列表--显示加载时的依赖项,列出二进制文件的节信息,查看动态节,列出并查看段,查看重定位节,反汇编,列出库中未定义的符号,列出动态符号,列出二进制文件或库的符号表,查看节中的数据,符号的类型。
这是一篇关于设备驱动的思维导图,主要内容包括:主要功能,设备驱动模型。阐述了设备驱动的主要功能、信号定义、设备驱动模型等内容。
这是一篇关于算法的思维导图,主要内容包括:云计算,内存管理算法,分布式同步算法,避免死锁算法,进程调度算法,磁盘调度算法。
编译和链接
工具链
gcc
-E
只进行预处理并把预处理结果输出
-c
只编译不链接
-o <filename>
指定输出文件名
-I
指定头文件路径
-e name
指定name为程序入口地址
-ffreestanding
编译独立的程序 不会自动链接C运行库 启动文件等
-g
在编译结果中加入调试信息
-ggdb
加入GDB调试器能够识别的格式的调试信息
-L <directory>
指定链接时查找的路径 多个路径之间用:隔开
-nostartfiles
不要链接启动文件 比如crtbegin.o crtend.o
-nostdlib
不要链接标准库文件 主要是C运行库
-nostdinc
使便器不在系统默认的头文件目录中寻找头文件 一般和-I联合使用 明确限定头文件的位置
-fnopic
关闭fPIC
-fnobuiltin
不接受内建函数
-MD
生成当前编译程序文件关联的详细信息 包含目标文件所依赖的所有源码文件 包括头文件 但是信息输出将导入.d文件中
-m32
生成32位机器上的代码
-Werror
所有的警告都变成错误 使得出现警告时也停止编译
-gdwarf2
附带输出调试信息
-O n
编译优化选项
-O 0
关闭所有编译器优化
-fno-stack-protector
关闭堆栈保护功能
-fno-inline-functions
关闭内联函数
-fno-builtin
禁止GCC编译器内置函数
-O 1
增加一些GCC优化代码选项
-fcprop-registers
因为在函数中把寄存器分配给变量 所以编译器执行第二次检查以便减少调度依赖性 并且删除不必要的寄存器复制操作
-fdefer-pop
这优化与在函数完成时如何进行操作有关 一般情况下 函数的输入值被保存在堆栈中并且被函数访问 函数返回时 输入值还在堆栈中 函数返回之后 输入值被立即弹出堆栈
-fdelayed-branch
试图根据指令周期重新安排指令 试图把尽可能多的指令移动到条件分支之前 以便充分利用处理器的指令缓存
-fguess-branch-probability
试图确定条件分支最可能的结果 并且相应得移动指令 和延迟分支技术类似 在编译时预测代码的安排
-fif-conversion
通过减少或删除条件分支 以及使用条件传送设置标志和使用运算技巧来替换它们 减少if-then语句中花费的时间量
-fif-conversion2
结合更加高级的数学特性 减少实现if-then语句所需的条件分支
-floop-optmize
通过删除在循环内没有改变值的变量赋值操作 减少循环内执行指令的数量 优化那些确定何时离开循环的条件分支 可用减少分支的影响
-fmerge-constants
试图合并相同的常量 编译器分析C或C++程序中用到的每个常量 并且比较它们
-fomit-frame-pointer
禁止使用EBP作为函数帧指针 对于叶子函数 不产生相应的栈帧
-O 2
增加一些GCC优化代码选项 进行一些额外的调整工作 (处理器指令调度)
-falign-functions
使函数按内存特定边界对齐
-fcaller-saves
指示编译器对函数调用保存和恢复寄存器 使函数能够访问寄存器值 如果调用多个函数 能够节省时间 因为只进行一次寄存器值的保存和恢复操作 而不是再每个函数调用中都进行
-fcrossjumping
对跨越调整的转换代码处理 以便组合分散在程序各处的相同代码 可用减少代码的长度
-fcse-follow-jumps
采用特别的通用子表达式消除技术扫描跳转指令 查找程序中通过任何其他途径都不会到达的目标代码
-fdelete-null-pointer-checks
扫描生成的汇编语言代码 查找检查空指针的代码 假设间接引用空指针将停止程序 如果在间接引用之后检查指针 他就不可能为空
-fexpensive-optimizations
这种技术执行对于编译来说是代价高昂的各种优化技术 可能对运行时的性能产生反面影响
-fforce-mem
在任何指令使用变量前 强制把存放在内存位置中的所有变量都复制到寄存器中 因为和访问内存中的值相比 处理器访问寄存器中的值要快的多
-fgcse
对生成的所有汇编代码执行全局通用表达式消除 试图分析生成的汇编代码并且结合通用片段 消除冗余的代码段
-foptimize-sibling-calls
处理递归的函数调用 通常递归的函数调用可以被展开为一系列一般的指令(而不是使用分支) 这样处理器的指令缓存能够加载展开的指令并且处理它们 和保持需要分支操作的单独函数相比更快
-fpeephole2
允许进行任何计算机特定的优化
-fregmove
试图重新分配mov指令中使用的寄存器 并且将其作为其他指令操作数 以使最大化捆绑寄存器的数量
-freorder-functions
允许重新安排指令块 以便改进分支操作和代码局部性
-freturn-cse-after-loop
在对任何已经进行过优化的循环中重新运行通用子表达式消除例程 确保在展开循环代码之后更进一步的优化汇编代码
-fsched-interblock
使编译器能够跨越指令块调度指令 可以非常灵活的移动指令以便等待期间完成的工作最大化
-fschedule-insns
试图重新安排指令 以便消除处理器等待数据 对于正进行浮点运算时有延迟的处理器来说 这使处理器在等待浮点结果时可以加载其他指令
-fstrength-reduce
对循环执行优化并且删除迭代变量 (迭代变量是捆绑到循环计数器的变量) 然后使用循环计数器变量执行数学操作的for-next循环
-fstric-aliasing
强制实行高级语言的严格变量规则 确保不在不同数据类型之间共享变量
-fthread-jumps
编译器处理汇编代码中的条件和非条件分支语句 一条跳转指令可能转移到另一条分支语句 通过一连串跳转 编译器确定多个跳转之间的最终目标并且把第一个跳转重定向到最终目标
-funit-at-a-time
指示编译器在运行优化例程之前读取整个汇编代码 使编译器可以重新安排不消化大量时间的代码以便优化指令缓存
-fweb
构建用于保持变量的伪寄存器 伪寄存器保护数据 可以使用各种其他优化技术进行优化(如cse和loop)
-O 3
增加一些GCC优化代码选项 进行一些额外的调整工作(处理器指令调度) 循环展开和其他一些与处理器特性相关的优化工作
-finline-functions
启动内联函数 不为函数创建单独的汇编代码 而是把函数代码包含在调度程序的代码中 对于多次被调用的函数来说 为每次函数调用复制函数代码 虽然对增大代码长度 但通过最充分的利用指令缓存代码而不是在每次函数调用时进行分支操作来提高性能
-fgcse-after-reload
重新加载生成却优化后的汇编代码 执行第二次gcse优化 帮助消除不同优化方式创建的任何冗余
-fnoomitframepointer
省略栈帧指针 backtrace利用调用栈帧信息把函数调用关系层层遍历出来
-shared
产生共享对象文件
-static
使用静态链接
告警
-Wall
对源码中多数编译警告进行启用
-Wunused-function
警告存在一个未使用的static函数的定义或者存在一个只声明却未定义的static函数
-Wunused-label
警告存在一个使用了却未定义或存在一个定义了却未使用的label
-Wunused-variable
警告存在一个定义了却未使用的局部变量或者非常量static变量
-Wunused-value
警告一个显式计算表达式的结果未被使用
-Wunused-parameter
警告一个函数的参数在函数的实现中并未被用到
-Wunreachable-code
警告代码中有不可达的代码
-fPIC
使用地址无关代码模式进行编译
-fPIE
使用地址无关代码模式编译可执行文件
-XLinker <option>
把option传递给连接器
-W1 <option>
把option传递给连接器
-ffunction-sections
将每个函数编译到独立的代码段
-fdata-sections
将全局/静态变量编译到独立的数据段
处理器相关
-mcpu
指定目标CPU的型号
-msoft-float
使用软浮点
-hfloat
使用硬浮点
ld
ld [-O3Mims[-]] [-T textaddr] [-llib_extension] [-o outfile] infile ...
-static
静态链接
-l<libname>(会对运行时产生影响)
指定链接某个库
-e name
指定name为程序入口
-b <input -format>
指定目标代码输入文件的格式
-r
合并目标文件 不进行最终链接
-L <directory>(仅在库构建时起作用)
指定链接时查找路径 多个路径用:隔开
-i
分离的指令与数据段输出
-ix
将库/local/lib/subdir/libx.a加入链接的文件列表中
-M
将链接时的符号和地址输出成一个映射文件
-m
在标准输出设备上显示已连接的模块
-3
产生具有32bit魔数的头结构 并且对-lx选项使用i386子目录
-O
产生具有16bit魔数的头结构 并且对-lx选项使用i86子目录
-o
指定输出文件名
-s
清除输出文件中的符号信息
-S
清除输出文件中的调试信息
-T<scriptfile>
指定链接脚本文件
-version-script <file>
指定符号版本脚本文件
-soname<name>
指定输出共享库的SONAME
-export-dynamic
将全局符号全部导出
-verbose
连接时输出详细信息
-rpath
指定链接时库查找路径
objdump
显示一个目标文件中所有的信息 反汇编.text段中的二进制指令
-a
列举.a文件中所有的目标文件
-b bfdname
指定BFD名
-C
对于C++符号名进行反修饰
-g
显示调试信息
-d
对包含机器指令的段进行反汇编
-D
对所有的段进行反汇编
-f
显示目标文件的文件头
-h
显示段表
-l
显示行号信息
-p
显示专有头部信息
-r
显示重定位信息
-R
显示动态链接重定位信息
-s
显示文件所有内容
-S
显示源代码和反汇编代码
-W
显示文件中包含有DWARF调试信息格式的段
-t
显示文件中的符号表
-T
显示动态链接符号表
-x
显示文件的所有文件头
-EB -EL
指定目标的大小端 将影响反汇编出来的指令
-i
显示对于 -b或者-m选项可用的架构和目标格式列表
-j name
仅仅显示指定名称为name的节的信息
-m machine
指定反汇编目标文件时使用的架构
ar
创建静态库 插入 删除 列出和提取成员
-x
从归档文件中提取所有的成员文件
as
as [-O3agjuw] [-b [bin]] [-lm [list]] [-n nmae] [-o objfile] [-s sym] srcfile
-O
使用16bit代码段
-3
使用32bit代码段
-a
开启GUN as ld的部分兼容性选项
-b
产生二进制文件后面可以跟文件名
-g
在目标文件中仅存入全局符号
-j
使所有跳转语句均为长跳转
-l
产生列表文件 后面可以跟随列表文件名
-m
在列表中扩展宏定义
-n
后面跟随模块名称(取代源文件名称放入目标文件中)
-o
产生目标文件 后面跟目标文件名
-s
产生符号文件 后面跟符号文件名
-u
将未定义符号作为输入的未指定段的符号
-w
不显示警告信息
strings
列出一个目标文件中所有可打印的字符串
objcopy
-I bfdname
指定输入文件的BFD标准格式名bfdname 可取值为elf32-little elf-big等
-O bfdname
指定输出文件的BFD标准格式名
-F bfdname
指定输入输出文件的BFD标准格式名 目标文件格式 只用于在目标和源直接传输数据 不转换
-j sectionname
只将由sectionname指定的节复制到输出文件 可以多次指定
-R sectionname
从输出文件中去除有sectionname指定的节 可以多次指定
-S
不从源文件复制符号信息和重定位信息
-g
不从源文件复制调试信息和相关的段
-G symbolname
只保留symbol为全局变量 让其他变量都是文件局部变量 这样外部不可见
-L symbolname
将变量symbol变成文件局部变量 可以多次指定
-x
不从源文件中复制非全局变量
strip
从目标文件中删除符号表信息
nm
列举目标文件符号
选项说明
-A
每个符号前显示文件名
-D
显示动态符号
-g
仅显示外部符号
-r
反序显示符号表
返回结果说明
列[1]-符号的起始地址
符号的类型
A: Global absolute
a: Local absolute
B: Global bss(出现在未初始化数据节中的符号)
b: Local bss(出现在未初始化数据节中的符号)
C: 通用符号
通用符号时未初始化数据 在链接时会出现许多名称相同的通用符号 无论符号出现在什么地方 通用符号都属于未定义引用
D: Global data(出现在已初始化数据节中的符号)
d: Local data(出现在已初始化数据节中的符号)
f: 源文件名称符号
G/g: 主要用于支持小型对象
已初始化数据节中的符号 一些目标文件格式允许更高效的访问小型数据对象
i: GNU相对于标准ELF符号类型的扩展
如果目标文件是PE格式文件 该类型说明符号在针对DLL实现的节中 如果目标文件是ELF格式文件 说明符号是一个间接函数
N: 调试符号
p: 出现在栈的展开节中的符号
R/r: 出现在只读数据节中的符号
S/s: 出现在未初始化数据节中的符号 用于支持小型对象
T: Global text(出现在代码中的符号)
t: Loacl text(出现在代码中的符号)
U: 未定义符号
u: 全局唯一的符号
V/v: 弱对象
当已定义的弱符号和已定义的正常符号一起链接时 已定义的正常符号可以使用且不会报错 当链接未定义的弱符号且该符号没有定义时 会将弱符号填充为0 且不会报错
W/w: 弱符号(没有被明确标记成弱对象的符号)
当已定义的弱符号和已定义的正常符号一起链接时 已定义的正常符号可以使用且不会报错 当链接为定义的弱符号且该符号没有定义时 该符号的值根据系统特定的行为来确定
-: a.out目标文件中的Stab符号(用于保存调试信息)
?: 未知符号 或是目标文件特定格式的符号
列[3]-符号的名称
size
显示目标文件段大小以及目标文件大小
text
文件中指令的大小
data
文件有初值的全局变量和静态变量大小
bss
文件中未赋初值或初值为0的全局变量和静态变量的大小
dec
text+data+bss
hex
dec的16进制表示
readelf
显示一个目标文件的完整结构 包括elf头中编码的所有信息
返回结果说明
ELF Header
Magic
给出了ELF文件的一些标识信息
7f 45 4c 46
表明该文件是ELF文件
01
表明该文件运行在32位的操作系统上
02
表明数据采用大端排序 低位在前
Entry point address
程序入口的虚拟地址(如果目标文件没有程序入口 可以为0) 在程序加载完成后 loader会将程序的焦点转移到该地址
Start of program headers
表明程序头部表在ELF文件中的位置
Start of section headers
表明节区头部表在ELF文件中的位置
Size of this header
表明ELF文件头部的大小 这个值是52 注意程序头部表在ELF文件中的位置也是52 说明程序头部表在文件中紧挨着ELF文件头部
Size of program headers
表明程序头部表每行的大小为32B
Number of program headers
表明程序头部表中共有8行(意味着程序中有8个段)
Size of section headers
表明节区头部表每行的大小为40B
Number of section headers
表明在节区头部表中共有38行(意味着程序中有38个节)
Section Headers
Type
对这个节的内容进行分类
NULL
此值标志节区头部是非活动的 没有对应的节区 此节区头部中的其他成员取值无意义
PROGBITS
此节区包含程序定义的信息 其格式和含义都由程序来解释
SYMTAB
此节区包含一个符号表
STRTAB
此节区包含字符串表 目标可能包含多个字符串表节区
RELA
此节区包含重定位表项 其中可能会有补齐内容
HASH
此节区包含符号哈希表 所有参与动态链接的目标都必须包含一个符号哈希表
DYNAMIC
此节区包含动态链接信息
NOTE
此节区包含以某种方式来标记文件的信息
NOBITS
这种类型的节区不占用文件中的空间
DYNSYM
作为一个完整的符号表 它可能包含很多对动态链接而言不必要的符号
INIT_ARRAY
在main函数之前 运行的函数指针数组
FINI_ARRAY
在退出main函数之后 运行的函数指针数组
Addr
如果节区将出现在进程的内存映像中 此成员给出节区的第一个字节应处的位置 否则此字段为0
Off
表示该节内容距离文件起始的偏移地址
Size
表示该节在文件中的大小
ES
某些节区中包含固定大小的项目 如符号表 对于这类节区 此成员给出每个表项的长度字节数 如果节区中并不包含固定长度表项的表格 此成员取值为0
Flag
表示该节的内存分配属性
A
表示分配内存
X
表示可执行
W
表示可写
Lk
给出节区头部表索引链接
AL
某些节区带有地址对齐约束
常用的节
.bss
包含将出现在程序的内存映像中的未初始化数据 (当程序开始执行 系统将把这些数据初始化为0) 此节区不占用文件空间 主要用来保存未初始化或初始化为0的全局变量和静态变量
.comment
包含了一些注释信息和版本控制信息
.ctors
保存已初始化并指向C++构造函数的指针
.data
此节区包含初始化了的数据 将出现在程序的内存映像中 主要用来保存初始化不为0的全局变量和静态变量
.data1
保存程序内存映射中的初始化数据 该节类型为SHT_PROGBITS 属性有SHF_ALLOC和SHF_WRITE
包含用于调试的符号信息的节区
debug_aranges
debug_pubnames
debug_info
debug_abbrev
debug_line
debug_frame
debug_str
debug_ranges
.dtors
保存已初始化并指向C++析构函数的指针
.dynamic
此节区包含动态链接信息
.dynsym
此节区包含了动态链接符号表
.fini
此节区包含了可执行的指令 是进程终止代码的一部分 程序正常退出时 系统将安排执行这里的代码
.gnu.version
保存版本符号表(ElfN_Half元素数组)
.gnu.version_d
保存版本符号定义(ElfN_Verdef结构表)
.gnu.version_r
保存版本符号所需的元素(ElfN_Verneed结构表)
.got
此节区包含全局偏移表 其与plt一起协作完成符号的动态查找
.got.plt
保存过程链接表
.hash
此节区包含一个符号哈希表
.init
此节区包含了可执行指令 是进程初始化代码的一部分 程序开始执行时 系统要在开始调用主程序入口之前执行这些代码
.interp
保存程序解释器的路径名
.line
保存符号调试信息的行号 描述了程序源代码和机器码之间的对应关系
.note
保存NoteSection格式信息
.note.GNU-stack
保存linux目标文件的栈属性声明 用于向GNU链接器说明目标文件需要依赖具有可执行权限的栈
.plt
此节区包含过程链接表
.relNAME
保存重定位信息 根据惯例 需要将NAME替换成进行重定位的节的名称
.relaNAME
保存重定位信息 根据惯例 需要将NAME替换成进行重定位的节的名称
.rodata
此节区包含只读数据 这些数据通常参与进程映像的只读代码段
.rodata1
保存进程映像中只读段的只读数据
.shrstrtab
保存节的名称
.strtab
此节区包含一个符号表
.symtab
此节区包含一个符号表
.text
此节区包含程序的可执行指令
.dynstr
此节区包含用于动态链接的字符串 这些字符串代表了与符号表项相关的名称
.rel.dyn
此节区中包含了重定位信息
.rel.plt
此节区中包含了重定位信息
.init_array
进程初始化的所运行的函数指针数组
.fini_array
进程退出时的所运行的函数指针数组
Program Headers
Type
各段的类型
PHDR
给出了程序头部表自身大小和位置(既包括在文件中也包括在内存中的信息)
INTERP
该段给出了启动进程的loader 该字符串将被当做解释器使用
LOAD
给出一个可加载的段 段大小由p_filesz和p_memsz描述 文件中的字节被映射到内存段开始处
DYNAMIC
给出动态链接信息
NOTE
给出附加信息的位置和大小
Offset
该段在距离文件开头的偏移地址
VirtAddr
给出段的第一个字节将被放到内存中的虚拟地址
PhysAddr
仅用于与物理地址相关的系统中
FileSiz
给出段在文件映像中所占的字节数 memsize和filesize可能不等 因为.bss节只占据内存空间 不占据文件空间
MemSiz
给出段在内存映像中所占的字节数 memsize和filesize可能不等 因为.bss节只占据内存空间 不占据文件空间
Flg
该段的属性
R-可读
E-可执行
W-可写
Align
该段要求字节对齐的属性
Section to Segment mapping 段与节的映射关系
参数选项
-a
显示全部信息
-h
显示elf文件开始的文件头信息
-l
显示程序头(段头)信息
-S
显示节头信息
-g
显示节组信息
-t
显示节的详细信息
-s
显示符合表段中的项
-e
显示全部头信息
-n
显示note段(内核注释)的信息
-r
显示可重定位段的信息
-u
显示unwind段信息
-d
显示动态段的信息
-V
显示版本段的信息
-A
显示CPU架构信息
-D
使用动态段中的符号表显示符号 而不是使用符号段
-x=number
以16进制方式显示指定段内内容 numer指定段表中断的索引 或字符串指定文件中的段名
-w
显示调试段中指定的内容
-I
显示符号 显示bucket list长度的柱状图
-v
显示readlelf版本信息
-H
显示readelf所支持的命令行选项
-W
宽行输出
ELF
Program header程序头
p_type
当前Program header所描述的段的类型
p_offset
段的第一个字节在文件中的偏移
p_vaddr
段的一个字节在内存中的虚拟地址
p_paddr
在物理内存定位相关的系统中,此项是为物理地址保留
p_filesz
段在文件中的长度
p_memsz
段在内存中的长度
p_flags
与段相关的标志
p_align
根据此项值来确定段在文件及内存中如何对齐
视图
链接视图-section节
面向程序编译和链接过程 它将ELF文件中所需要保存的信息按照信息的类型 格式的不同 分别保存在文件的不同区域 这些区域称为section节 在ELF文件中又包含了一个这些节区位置的索引(节区头部表)
执行视图-segment段
面向程序的加载和运行 将节区按照运行时的需要划分为不同的组 称为segment段 在ELF文件中又引入了程序头部表
.debug段
用于保存调试信息
.dynamic段
用于保存动态链接信息
.fini段
用于保存进程退出时的执行程序 当进程结束时, 系统会自动执行这部分代码
.init段
用于保存进程启动时的执行程序 当进程启动时, 系统会自动执行这部分代码
.rodata段
用于保存只读数据, 如const修饰的全局变量、 字符串常量
.symtab段
用于保存符号表
ELF文件头
e_ident
以表示ELF文件的字符,以及其他一些与机器无关的信息
e_type
标识的是该文件的类型
e_machine
运行该程序需要的体系结构
e_version
文件的版本
e_entry
程序的入口地址
e_phoff
Program header table 在文件中的偏移量(以字节计数)
e_shoff
Section header table 在文件中的偏移量(以字节计数)
e_flags
对IA32而言,此项为0
e_ehsize
ELF header大小(以字节计数)
e_phentsize
Program header table中每一个条目的大小
e_phnum
Program header table中有多少个条目
e_shentsize
Section header table中的每一个条目的大小
e_shnum
Section header table中有多少个条目
e_shstrndx
包含节名称的字符串是第几个节(从零开始计数)
a.out格式的目标文件
执行头部分
内核使用struct exec执行头部分的参数信息把执行文件加载到内存中并执行 链接程序使用这些参数将一些模块文件组合成一个可执行文件 这是目标文件唯一必要的部分
代码区
含义程序执行时被加载到内存中的指令代码和相关数据 以只读的形式被加载
数据区
含有已经初始化过的数据 总是被加载到可读写的内存中
代码重定位部分
包含链接程序使用的记录数据 在组合目标模块文件时用于定位代码段中的指针或地址
数据重定位部分
包含链接程序使用的记录数据 用于数据段中指针的重定位
符号表部分
包含链接程序使用的记录数据 用于二进制目标模块文件之间对命名的符号(变量和函数)进行交叉引用
System.map符号文件格式说明
字符串表部分
含有与符号名对应的字符串
ldd
列出一个可执行文件在运行时所需要的共享库
addr2line
将地址转换成文件,行号
dd
用于以指定大小的块读取 转换并输出数据
if=文件名
输入文件名 默认为标准输入 /dev/zero:该设备无穷尽的提供0 /dev/null:代表一个无穷的空二进制流 从该文件什么也读不到
of=文件名
输出文件名 默认为标准输出 /dev/null:所有写入该文件的内容都会永远丢失
ibs=bytes
一次读入bytes字节 (指定输入块大小为bytes字节)
obs=bytes
一次输出bytes字节 (指定输出块大小为bytes字节)
bs=bytes
同时设置读入/输出块大小为bytes字节
cbs=bytes
一次转换bytes字节(指定转换缓冲区大小)
skip=blocks
从输入文件开头跳过blocks个块后再开始复制
seek=blocks
从输出文件开头跳过blocks个块后再开始复制
count=blocks
仅复制blocks个块 块大小等于ibs指定的字节数
conv
ascii
转换ebcdic码为ASCII码
ebcdic
转换ASCII码为ebcdic码
ibm
转换ASCII码为alternate ebcdic码
block
把每一行转换为cbs长 不足部分用空格填充
unblock
每一行的长度都为cbs长 尾部的空格替换为换行符
lcase
把大写字符转换为小写字符
ucase
把小写字符转换为大写字符
swab
交换输入的每对字节
noerror
出错时不停止
notrunc
不截短输出文件
sync
将每个输入块填充到ibs字节 不足部分用NUL字符补齐
gdb
选项
-cd
设置工作目录
-q
安静模式 不打印介绍信息和版本信息
-d
添加文件查找路径
-s
设置读取的符号表文件
命令
file <文件名>
加载被调试的可执行程序文件
r
运行被调试的程序 如果此前没有设置过断点 则执行完整个程序 如果有断点 则程序暂停在第一个可用断点处
c
继续执行被调试程序 直至下一个断点或程序结束
b
设置断点 可以使用行号 函数名称 代码地址等方式指定断点位置
d
删除指定编号的某个断点或删除所有断点 断点编号从1开始递增
s
执行一行源程序代码 如果此行代码中有函数调用 则进入该函数 (相当于单步跟踪进入)
n
执行一行源程序代码 此行代码中的函数调用也一并执行 (相当于单步跟踪跳过)
p
显示指定变量的值
display/undisplay <编号>
设置程序中断后欲显示的数据及其格式
i
用于显示各类信息
q
退出gdb调试环境
help [命令名称]
gdb帮助命令 提供对gdb各种命令的解释说明
list
显示代码
bt
打印函数调用栈帧跟踪信息
frame
显示当前运行的栈帧
编译
kconfig
source条目
用于读入另一个Kconfig文件 "source" <prompt>
config条目
配置一个选项以生成一个变量 这个变量会连同它的值一起被写入配置文件.config中
menu条目
用于生成菜单 menu内可以包含多个config条目 格式如下: "menu" <prompt> <menu options> <menu block> "endmenu"
choice条目
将多个类似的config配置选项组合在一起 供用户单选或多选 格式如下: "choice" <choice options> <choice block> "endchoice"
comment条目
用于定义一些帮助信息 格式如下: "comment" <prompt> <comment options>
内核编译和安装
1. 清理工作
1. 清理原有的内核编译设置参数和残留文件
# cd 内核源文件目录 # make mrproper
2. 清理上一次的编译编译的残留文件,但不清除编译设置参数
# cd 内核源文件目录 # make clean
2. 挑选编译内核的参数
# make menuconfig
通过文字界面的菜单选择编译参数
# make oldconfig
把 ./.config 文件中的参数作为默认值
把不在./.config 文件中的参数作为选项供用户作为编译内核的选项选择
3. 编译
# make vmlinuz
编译未经压缩的内核
# make modules
仅编译内核模块
# make bzImage
编译经默认压缩的内核
# make localconfig
使用当前内核配置来配置待编译内核 自动生成.config文件
自动检测当前系统中使用的模块
# make all 编译上述全部内容
#make install
使用发行版/sbin/installkernel程序安装内核映像
#make bzdisk
创建一个软盘镜像 并写入/dev/fd0设备
#make fdimage
创建一个可引导的软盘镜像
#make isoimage
创建一个可引导的CD-ROM镜像
#make checkstack
创建一个占用大部分内核栈空间的函数列表
#make namespacecheck
创建一个所有内核符号和其命名空间的列表
#make rpm
首先构建内核 然后打包成可供安装的RPM包
#make rpm-pkg
构建包含内核源代码的RPM源码包
#make binrpm-pkg
构建包含已编译内核和模块的RPM包
#make deb-pkg
构建包含已编译内核和模块的Debian格式的包
#make tar-pkg
构建包含已编译内核和模块的tarball
#make targz-pkg
构建经过gzip压缩的包含已经编译内核和模块的tarball
#make tarbz2-pkg
构建经过bzip2压缩的包含已经编译内核和模块的tarball
#make xmldocs
生成XML DocBook格式的内核文档
#make psdocs
生成PostScript格式的内核文档
#make pdfdocs
生成PDF格式的内核文档
#make htmldocs
生成HTML格式的内核文档
#make mandocs
将内核文档构建为能够使用installmandocs目标安装的一系列手册
4. 安装内核
1.把 bzImage 文件拷贝到 /boot 目录下且命名成 vmlinuz-xxx
2.把 .config 配置文件拷贝到 /boot 目录下,且命名为 config-xxx
3.# chmod a+x /boot/vmlinuz-xxx
4.# cp System.map /boot/System.map-xxx
5. # gizp -c Module.symvers > /boot/symvers-xxx.gz
6. # restorecon -Rv /boot
5. 创建对应的 initramfs
# dracut -v /boot/initramfs-xxx.img xxx
6. 编辑grub菜单
# grub-mkconfig -o /boot/grub/grub.cfg
make&makefile
make工作原理
make 会在当前目录下找名字叫Makefile或makefile的文件
找文件中的第一个目标文件target 并把这个文件作为最终的目标文件
如果目标文件不存在 或是目标所依赖的后面的 .o 文件的文件修改时间要比目标文件新 那么 他就会执行后面所定义的命令来生成目标文件
如果目标所依赖的.o 文件也不存在 那么 make 会在当前文件中找目标为.o 文件的依赖性 如果找到则再根据那一个规则生成.o 文件
根据.c和.h生成.o文件 再用.o文件生成目标文件
隐晦规则-自动推导
编译 C 程序的隐含规则
只要 make 看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中 如果 make找到一个 whatever.o 那么 whatever.c就会是 whatever.o 的依赖文件
编译 C++程序的隐含规则
“<n>.o”的目标的依赖目标会自动推导为“<n>.cc”或是“<n>.C”
编译 Pascal 程序的隐含规则
“<n>.o”的目标的依赖目标会自动推导为“<n>.p”
编译 Fortran/Ratfor 程序的隐含规则
“<n>.o”的目标的依赖目标会自动推导为“<n>.r”或“<n>.F”或“<n>.f”
预处理 Fortran/Ratfor 程序的隐含规则
“<n>.f”的目标的依赖目标会自动推导为“<n>.r”或“<n>.F”
编译 Modula-2 程序的隐含规则
“<n>.sym”的目标的依赖目标会自动推导为“<n>.def”
汇编和汇编预处理的隐含规则
“<n>.o” 的目标的依赖目标会自动推导为“<n>.s”
链接 Object 文件的隐含规则
“<n>”目标依赖于“ <n>.o”,通过运行 C 的编译器来运行链接程序生成
Yacc C 程序时的隐含规则
“<n>.c”的依赖文件被自动推导为“n.y”
Lex C 程序时的隐含规则
“<n>.c”的依赖文件被自动推导为“n.l”
Lex Ratfor 程序时的隐含规则
“<n>.r”的依赖文件被自动推导为“n.l”
从 C 程序创建 Lint 库的隐含规则
“<n>.ln” (lint 生成的文件) 的依赖文件被自动推导为“n.c”
显示规则
“规则”就是描述在什么情况下、如何重建规则的目标文件 通常规则中包括了目标的依赖关系(目标的依赖文件)和重建目标的命令。 make 执行重建目标的命令,来创建或者重建规则的目标 (此目标文件也可以是触发这个规则的上一个规则中的依赖文件) 规则包含了文件之间的依赖关系和更新此规则目标所需要的命令
指出要生成的文件,文件的依赖文件,生成的命令
make执行步骤
读入所有的 Makefile
读入被 include 的其它 Makefile
初始化文件中的变量
推导隐晦规则,并分析所有规则
为所有的目标文件创建依赖关系链
根据依赖关系,决定哪些目标要重新生成
执行生成命令
makefile规则语法
targets : prerequisites [Tab]command target:规则的目标 通常是最后需要生成的文件名或者为了实现这个目的而必需的中间过程文件名 可以是.o文件、也可以是最后的可执行程序的文件名等 另外,目标也可以是一个make执行的动作的名称,如目标“ clean”,我们称这样的目标是“伪目标” prerequisites:规则的依赖 生成规则目标所需要的文件名列表 通常一个目标依赖于一个或者多个文件 command:规则的命令行 是规则所要执行的动作 它限定了 make 执行这条规则时所需要的动作 一个规则可以有多个命令行,每一条命令占一行 注意: 每一个命令行必须以[Tab]字符开始 [Tab]字符告诉 make 此行是一个命令行 make 按照命令完成相应的动作
特殊目标
.PHONY
“伪目标”并不是一个文件,只是一个标签(“伪目标”的取名不要和文件名重名) 可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”(解决和文件重名问题) 由于“伪目标”不是文件,所以 make 无法生成它的依赖关系和决定它是否要执行 我们只有通过显示地指明这个“目标”才能让其生效
.SUFFIXES
特殊目标“ SUFFIXES”的所有依赖指出了一系列在后缀规则中需要检查的后缀名 (就是当前make需要处理的后缀)
.DEFAULT
目标“ .DEFAULT”所在规则定义的命令,被用在重建那些没有具体规则的目标(明确规则和隐含规则) 就是说一个文件作为某个规则的依赖,但却不是另外一个规则的目标时 Make 程序无法找到重建此文件的规则,此种情况时就执行“ .DEFAULT”所指定的命令
.PRECIOUS
目标“ .PRECIOUS”的所有依赖文件在make过程中会被特殊处理: 当命令在执行过程中被中断时, make不会删除它们 而且如果目标的依赖文件是中间过程文件 同 样这些文件不会被删除 这一点目标“ .PRECIOUS”和目标“ .SECONDAY”实现的功能相同 目标“ .PRECIOUS”的依赖文件也可以是一个模式,例如“ %.o” 这样可以保留有规则创建的中间过程文件
.INTERMEDIATE
目标“ .INTERMEDIATE”的依赖文件在make时被作为中间过程文件对待 没有任何依赖文件的目标“ .INTERMEDIATE”没有意义
.SECONDARY
目标“ .SECONDARY”的依赖文件被作为中间过程文件对待 但这些文件不会被自动删除 没有任何依赖文件的目标“ .SECONDARY”的含义是: 将所有的文件作为中间过程文件(不会自动删除任何文件)
.DELETE_ON_ERROR
如果在Makefile中存在特殊目标“ .DELETE_ON_ERROR” make在执行过程中,如果规则的命令执行错误,将删除已经被修改的目标文件
.IGNORE
如果给目标“ .IGNORE”指定依赖文件,则忽略创建这个文件所执行命令的错误 给此目标指定命令是没有意义的 当此目标没有依赖文件时,将忽略所有命令执行的错误
.LOW_RESOLUTION_TIME
目标“ .LOW_RESOLUTION_TIME”的依赖文件被 make 认为是低分辨率时间戳文件 给目标“ .LOW_RESOLUTION_TIME”指定命令是没有意义的 通常文件的时间辍都是高分辨率的, make 在处理依赖关系时、 对规则目标-依赖文件的高分辨率的时间戳进行比较,判断目标是否过期 将命令创建的文件作为目标“ .LOW_RESOLUTION_TIME”的依赖 声明这个文件是一个低分辨率时间辍的文件 将文件作为目标“ .LOW_RESOLUTION_TIME”的依赖后 只要规则中目标和依赖文件的时间戳中的初始时间相等,就认为目标已经过期 这个特殊的目标主要作用是: 弥补系统在没有提供修改文件高分辨率时间戳机制的情况下,某些命令在 make 中的一些缺陷
.SILENT
出现在目标“ .SILENT”的依赖列表中的文件 make 在创建这些文件时 不打印出重建此文件所执行的命令 没有任何依赖文件的目标“ .SILENT”告诉make在执行过程中不打印任何执行的命令 现行版本make支持目标“ .SILENT”的这种功能和用法是为了和旧版本的兼容 在当前版本中如果需要禁命令执行过程的打印,可以使用make的命令行参数“ -s”或者“ --silent”
.EXPORT_ALL_VARIABLES
此目标应该作为一个简单的没有依赖的目标,它的功能含义是将之后所有的变量传递给子make进程
.NOTPARALLEL
Makefile 中,如果出现目标“ .NOPARALLEL”,则所有命令按照串行方式执行 即使存在 make 的命令行参数“ -j”。但在递归调用的字 make 进程中,命令可以并行执行 此目标不应该有依赖文件,所有出现的依赖文件将被忽略
自动化变量
$@
表示规则中的目标文件集 在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合
$%
仅当目标是函数库文件中,表示规则中的目标成员名
$<
依赖目标中的第一个目标名字 如果依赖目标是以模式( 即"%")定义的,那么"$<"将是符合模式的一系列的文件集 注意,其是一个一个取出来的
$?
所有比目标新的依赖目标的集合 以空格分隔
$^
所有的依赖目标的集合 以空格分隔 如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份
$+
所有依赖目标的集合 不去除重复的依赖目标
$*
这个变量表示目标模式中"%"及其之前的部分 这个变量对于构造有关联的文件名是比较有较 如果目标中没有模式的定义,那么"$*"也就不能被推导出 但是,如果目标文件的后缀是 make 所识别的 那么"$*"就是除了后缀的那一部分
函数
subst
$(subst <from>,<to>,<text>) 名称:字符串替换函数——subst 功能:把字串<text>中的<from>字符串替换成<to> 返回:函数返回被替换过后的字符串
patsubst
$(patsubst <pattern>,<replacement>,<text>) 名称:模式字符串替换函数——patsubst 功能:查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern> 如果匹配的话,则以<replacement>替换 这里,<pattern>可以包括通配符“%”, 表示任意长度的字串 如果<replacement>中也包含“%” 那么, <replacement>中的这个“%”将是<pattern>中的那个“%”所代表的字串 (可以用“\”来转义, 以“\%”来表示真实含义的“%”字符) 返回:函数返回被替换过后的字符串
strip
$(strip <string>) 名称:去空格函数——strip 功能:去掉<string>字串中开头和结尾的空字符 返回:返回被去掉空格的字符串值
findstring
$(findstring <find>,<in>) 名称:查找字符串函数——findstring 功能:在字串<in>中查找<find>字串 返回:如果找到,那么返回<find>,否则返回空字符串
filter
$(filter <pattern...>,<text>) 名称:过滤函数——filter 功能:以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词 可以有多个模式 返回:返回符合模式<pattern>的字串
filter-out
$(filter-out <pattern...>,<text>) 名称:反过滤函数——filter-out 功能:以<pattern>模式过滤<text>字符串中的单词,去除符合模式<pattern>的单词。可以有多个模式 返回:返回不符合模式<pattern>的字串
sort
$(sort <list>) 名称:排序函数——sort 功能:给字符串<list>中的单词排序(升序) 返回:返回排序后的字符串
word
$(word <n>,<text>) 名称:取单词函数——word 功能:取字符串<text>中第<n>个单词。(从一开始) 返回:返回字符串<text>中第<n>个单词 如果<n>比<text>中的单词数要大 那么返回空字符串
wordlist
$(wordlist <s>,<e>,<text>) 名称:取单词串函数——wordlist 功能:从字符串<text>中取从<s>开始到<e>的单词串。<s>和<e>是一个数字 返回:返回字符串<text>中从<s>到<e>的单词字串 如果<s>比<text>中的单词数要大,那么返回空字符串 如果<e>大于<text>的单词数,那么返回从<s>开始,到<text>结束的单词串
words
$(words <text>) 名称:单词个数统计函数——words 功能:统计<text>中字符串中的单词个数 返回:返回<text>中的单词数
firstword
$(firstword <text>) 名称:首单词函数——firstword 功能:取字符串<text>中的第一个单词 返回:返回字符串<text>的第一个单词
dir
$(dir <names...>) 名称:取目录函数——dir 功能:从文件名序列<names>中取出目录部分(目录部分是指最后一个反斜杠(“/”)之前的部分) 如果没有反斜杠,那么返回“./” 返回:返回文件名序列<names>的目录部分
notdir
$(notdir <names...>) 名称:取文件函数——notdir 功能:从文件名序列<names>中取出非目录部分 非目录部分是指最后一个反斜杠(“ /”)之后的部分 返回:返回文件名序列<names>的非目录部分
suffix
$(suffix <names...>) 名称:取后缀函数——suffix 功能:从文件名序列<names>中取出各个文件名的后缀 返回:返回文件名序列<names>的后缀序列,如果文件没有后缀,则返回空字串
basename
$(basename <names...>) 名称:取前缀函数——basename 功能:从文件名序列<names>中取出各个文件名的前缀部分 返回:返回文件名序列<names>的前缀序列,如果文件没有前缀,则返回空字串
addsuffix
$(addsuffix <suffix>,<names...>) 名称:加后缀函数——addsuffix 功能:把后缀<suffix>加到<names>中的每个单词后面 返回:返回加过后缀的文件名序列
addprefix
$(addprefix <prefix>,<names...>) 名称:加前缀函数——addprefix 功能:把前缀<prefix>加到<names>中的每个单词后面 返回:返回加过前缀的文件名序列
join
$(join <list1>,<list2>) 名称:连接函数——join。 功能:把<list2>中的单词对应地加到<list1>的单词后面 如果<list1>的单词个数要比<list2>的多,那么,<list1>中的多出来的单词将保持原样 如果<list2>的单词个数要比<list1>多,那么,<list2>多出来的单词将被复制到<list2>中 返回:返回连接过后的字符串
foreach
$(foreach <var>,<list>,<text>) 把参数<list>中的单词逐一取出放到参数<var>所指定的变量中 然后再执行<text>所包含的表达式 每一次<text>会返回一个字符串,循环过程中,<text>的所返回的每个字符串会以空格分隔, 最后当整个循环结束时 <text>所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函数的返回值
if
$(if <condition>,<then-part>,<else-part>)
call
$(call <expression>,<parm1>,<parm2>,<parm3>...) 用来创建新的参数化的函数 可以写一个非常复杂的表达式,这个表达式中,你可以定义许多参数 然后你可以用 call 函数来向这个表达式传递参数
origin
$(origin <variable>) 指明变量出处 返回值: “undefined”- variable没有定义过 “default”-variable是一个默认的定义(比如“CC”这个变量) "environment”-variable是一个环境变量 “file”-variable被定义在 Makefile 中 “command line”-variable是被命令行定义的 “override”-variable是被 override 指示符重新定义的 “automatic”-variable是一个命令运行中的自动化变量
error
$(error <text ...>) 产生一个致命的错误,<text ...>是错误信息
warning
$(warning <text ...>) 它并不会让 make 退出,只是输出一段警告信息 而 make 继续执行
构造
词法分析器
扫描器
算法
有限自动机
整数有限自动机
字符有限自动机
字符串有限自动机
界符有限自动机-示例
单字节界符
+
-
*
/
%
&
>
<
=
!
,
:
;
(
)
[
]
{
}
文件结束符-1
双字节界符
++
--
>=
<=
==
!=
&&
||
空白字符有限自动机
注释有限自动机
词法记号类图
词法记号
标签
解析器
解析器的输入是扫描器产生的线性字符序列 输出是词法记号序列 依赖于词法记号和有限自动机的实现完成词法记号的解析
实现方式
基于表驱动的方式
缺点
保存状态转移表需要大量的存储空间 构建状态转移表对词法记号的定义有很强的以来 一旦更改了词法记号的定义 状态转移表变化很大
灵活性较差
基于表驱动的词法分析在每次读入一个字符后都会发生状态转移 大量的查表 状态比较和状态处理降低了词法分析器的性能
硬编码方式
硬编码方式的词法分析不需要显式的确立有限自动机的状态 它使用程序控制结构直接对词法记号进行解析 根据词法记号本身的含义 使用代码解析词法记号的内容
语法分析器
声明与定义 ->代表推导过程
说明
<defdata>表示所有变量的定义
<varrdef>表示变量和数组的定义
<init>表示初始化部分
<expr>表示表达式
<deflist>表示除第一个变量定义外的剩余部分
<fun>表示函数的定义(除去返回类型的部分)
<para>表示形式参数列表
<paradata>表示一个形式参数去除类型的部分
<paralist>表示形式参数列表除第一个形式参数之外的部分
<type><paradata>表示一个形式参数
<type><paradata><paralist>表示完整的形式参数列表
<block>表示函数体的内容
<subprogram>表示子程序的内容
<localdef>表示局部变量定义
<statement>表示语句
<operator>表示通用的表达式运算符
<oprand>表示运算符作用的对象
变量
变量定义基础产生式 <defdata>->ID <varrdef> | MUL ID <init> <varrdef>->LBRACK NUM RBRACK | <init> <init>->ASSIGN <expr> | ε
多变量定义产生式 <deflist>->COMMA <defdata><deflist> | SEMICON
<deflist>使用左递归定义表示任意多个COMMA <defdata>的组合 并使用分号词法记号SEMICON终止左递归的无限推导过程 这样 使用非终结符组合<defdata><deflist>就可用表示所有的变量定义结构
表达式
未经处理的表达式文法 <expr>-><oprand><exprtail> <exprtail>-><operator><operand><exprtail> | SEMICON
考虑运算符优先级的表达式文法
函数
函数产生式 <def>->ID <idtail> | MUL ID <init><deflist> <idtail>-><varrdef><deflist> | LPAREN <para> RPAREN <functail> <para>-><type><paradata><paralist> | ε <paralist>->COMMA <type><paradata><paralist> | ε <paradata>->MUL ID | ID <paradatatail> <paradatatail>->LBRACK NUM RBRACK | ε <functail>->SEMICON | <block> <block>->LBRACE<subprogram>RBRACE <subprogram>-><localdef><subprogram><statement><subprogram> | ε
语法树
未经处理的表达式a+b*c抽象语法树 注意:这个意义上的结果是错误的
考虑运算符优先级的表达式a+b*c抽象语法树 将高优先级运算符形成的表达式整体作为低优先级运算符形成的表达式的操作数
汇编器
链接
链接器和装载器
策略
1.链接器识别自身符号解析的局限性
创建动态库时 链接器要明确分清不同部分代码的关系 同时准确的识别出将代码段加载到不同地址范围中时会失效的符号引用 首先动态库内存映像的地址范围是从0开始的 链接器处理可执行文件时 不会将地址范围的起始地址设置成0 在加载阶段前 如果链接器发现某些符号的地址无法解析时就会停止解析 取而代之会使用临时值填充未解析符号
2.链接器统计失效符号引用 准备修复提示 并将提示嵌入二进制文件中
动态库中只要汇编指令需要绝对地址 指令中的引用就会失效 在完成动态库构建的链接阶段 链接器就可以识别出那些出现绝对地址的地方 并让装载器知道这些提示信息 这些提示包含:{ 装载器在完成整个进程的最终内存映射布局后需要修补的地址 装载器为了正确修补未解析引用需要执行的正确动作} 链接器将重定位提示写入二进制文件重定位节(.rel.dyn)以便于装载器读取这些信息
3.装载器遵循链接器的重定位提示 并在完成地址转换后根据提示进行修复
装载器读取由链接器创建的动态库 读取动态库中装载器段 并将所有数据放置到内存映射中 (存放在可执行文件代码附近) 装载器定位rel.dyn节 读取链接器预留的提示 并根据这些提示对原来的动态库进行修补 完成修补后 就可以准备使用内存映射启动进程了
技术
装载重定位LTR
缺点
使用变量或函数地址值来修补动态库代码 仅对首先加载动态库的程序上下文有意义 在其他程序的上下文中 原先的代码修改就毫无用处
如果多个应用程序同时需要一个动态库的服务 就意味着会在内存中存放同一个动态库的多份副本
需要的代码修改量和引用数量成正比 代码中对某变量进行了多少次引用 或对某函数进行了多少次调用 就需要修补多少代码 如果应用程序需要加载大量的动态库 那么程序启动时 加载时间就会显著增加 (有严重的启动延迟)
可写代码段(.text)造成了一个潜在的安全威胁 同时希望一次性将动态库加载入物理内存中 并将其映射到多个不同应用程序内存映射的不同地址中 是不可能完成的
位置无关码PIC
引入间接的额外步骤来避免对动态库代码段指令的不必要的直接修改 引用外部符号的指令获取符号地址需要2个步骤: 1.为了获取符号地址 首先使用mov指令访问一个地址 该位置存放了实际的符号地址 2.将该地址出的数据(所需符号的地址)加载到有效的CPU寄存器中 这样就可以将该寄存器作为后续指令的操作数 这些指令就可以访问到实际的符号地址了 (如果是数据 则为mov指令 如果是函数 则为call指令)
延迟绑定
汇编指令引用符号时 都会去引用一个中介地址来获取实际的符号地址 如果想让这样的代码生效 则需要初始化全局符号表 这会使程序加载出现明显的停滞 实际上 除非我们需要引用这些符号 否则装载器不会花时间去设置.got节和.git.plt节的内容 只有当程序启动后 且程序执行流程遇到指令引用了地址保存在.git节和.git.plt节中的符号时 装载器才会去设置这2个节中的数据 这就是延迟绑定 可以使应用程序启动得更快
内存映射 &共享内存
只需一份加载到某一进程中内存映射的动态库副本 就能映射到任何应用程序中(通过内存映射实现共享)
共享库
加载一个共享库
1.进程会加载该共享库的txt段和数据段 同时为这个共享库计数加一
2.进程查找该共享库的dynamic节 查看其所依赖的共享库
3.检查所依赖的库是否已经被加载 如果已经被加载 则为这个共享库计数加一 如果未被加载 则加载其txt段和data段 然后为这个共享库计数加一
4.再查找这些库是否有所依赖的库 重复上面步骤
卸载共享库
1.将共享库的计数减一 如果该共享库的依赖计数为0 则卸载该共享库
2.进程查找该共享库的dynamic节 查看其所依赖的共享库
1.为每个查到的共享库的计数减一 如果共享库的依赖计数为0 则卸载该共享库
4.再查找这些库是否有所依赖的库 重复上面步骤
定位动态库的优先级
1.预加载库
通过LD_PRELOAD指定
通过/etc/ld.so.preload文件
2.LD_LIBRARY_PATH环境变量
3.runpath(DT_RUNPATH环境变量)
4.ld.so.cache
5.默认库路径(/lib和/usr/lib)
类型
静态链接
在程序运行之前 先将各目标模块及它们所需的库函数链接成一个完整的可执行程序 以后不再拆开
优点
适用范围比较广 无须担心用户机器缺少某个库函数
缺点
修改或更新某个目标模块时无用重新打开装入模块 不仅涉及效率问题 且很多时候是不可能实现的
每个模块必须含有目标模块的复制 无法实现共享
装入时动态链接
将用户程序编译后所得到的一组目标模块 采用边装入边链接的方式装入内存
优点
便于修改和更新 由于各模块时分开存放的 非常容易修改或更新各目标模块
便于实现对目标模块的共享 很容易将一个目标模块链接到几个应用模块上 实现多个应用模块对目标模块的共享
缺点
由于应用程序事先无法确定本次要运行的模块 因此该程序的所有模块要全部装入内存 并在装入时链接在一起 有部分目标模块不会被运行 但也要装入 低效且浪费空间
运行时动态链接
将某些目标模块的链接推迟到程序执行时才进行
优点
在运行时动态链接过程中未用到的目标模块都不会被调入内存和链接 不仅加快程序的装入过程 而且大大节省内存空间
链接环境变量
LD_ASSUME_KERNEL
限定内核的版本 如果低于这个版本 ld.so就拒绝执行
LD_BIND_NOW
可以立刻绑定 而不是延迟绑定
LD_LIBRARY_PATH
在编译的时候指定去哪里搜索库 ld.so就按这个搜索(-rpath)
LD_PRELOAD
指定在程序执行之前先加载的库 并且在加载其他库之前先解析
LD_AUDIT
可以在程序运行时调用外部的audit接口
LD_BIND_NOT
GOT和PLT表解析后就不更新了 下次调用还解析
LD_DEBUG
ld.so在执行程序的时候打印连接器的调试信息 打印到LD_DEBUG_OUTPUT指定的位置
LD_DYNAMIC_WEAK
允许程序中定义的弱符号使用glibc中定义的版本
LD_POINTER_GUARD
安全性增强 用于管理指针
LD_PROFILE
定义那个共享对象被统计并写入到文件LD_PROFILE_OUTPUT中
LD_SHOW_AUXV
查看内核在执行ELF文件的时候传输给用户空间的信息
LD_PREFER_MAP_32BIT_EXEC
优先使用32位的分支预测方法 性能相关
链接过程
目标文件中每个节的起始地址都被临时设置成0 等待链接时调整 在程序构建的链接阶段会确定程序内存映射中每个独立节的实际地址范围
重定位
将分散在单独目标文件中不同类型的节拼接到程序的内存映射节中
解析引用
检查拼接到程序内存中的节
找出哪些部分代码产生了外部调用
计算该引用的精确地址(在内存映射中的地址)
将机器指令中的伪地址替换成程序内存映射的实际地址 (完成引用的解析)