读书笔记之: 程序员的自我修养——链接,装载与库(第1,2部分)
第1部分 简介




第1部分 简介
第1章 温故而知新
1. 早期计算机的硬件结构

北桥芯片:主要处理高速芯片
南桥芯片:主要处理低速设备

2. 内存的分配
在早期的计算机中, 操作系统对内存的管理存在很大的问题.
(1) 地址空间的不隔离

(2) 内存使用率低

(3) 程序运行的地址不确定

解决方法就是增加一个中间层, 将程序给出的地址作为虚拟的地址, 然后通过某种映射方法将虚拟地址映射到实际的物理地址.

3. 虚拟内存到物理内存的映射: 分段

分段的方法解决了地址空间不隔离和程序运行的地址不确定这两个问题, 但是没有解决内存使用率低的问题.
4. 分页机制

现代操作系统是将分段和分页机制联合起来使用的: 相同的段进行一块映射.
5. 现代操作系统中的线程
一个进程中的多个线程之间是共享资源的, 但是线程也拥有自己的私有资源.

6. 可重入与线程安全

7. 本章小结

第2部分 静态链接
第2章 编译与链接
1. 编译器的职责

2. 链接过程
主要包括地址和空间分配(Address and Storage Allocation) , 符号决议(Symbol Resolution) 和重定位(Relocation).
第3章 目标文件中有什么
1. 目标文件的格式
主要有PE和ELF格式两种.

ELF文件格式的分类


2. 目标文件是什么样的
目标文件主要是编译后的机器指令代码, 数据还有一些链接时需要的信息, 比如符号表, 调试信息, 字符串等.
这些信息按照不同的属性, 以section的形式存储.
下面是一个例子:


3. 将编译后的程序和指令分开存放
程序源代码编译后主要分为两种section: 程序指令和程序数据. 代码段属于程序指令, 而数据段和.bss段属于程序数据.
将程序指令和数据分开存放有很多的好处:
(1) 指令和数据的权限是不一样的.

(2) 分开存放便于CPU分开建立缓存, 提高读取速度

(3) 程序指令是只读的, 多个程序之间可以实现共享.

4. 一个简单的例子
SimpleSection.o
C代码如下:
int printf(const char *format,...);
int global_init_var=84;
int global_uninit_var;
void func1(int i){
printf("%d\n",i);
}
int main(void){
static int static_var=85;
static int static_var2;
int a=1;
int b;
func1(static_var+static_var2+a+b);
return a;
}
利用GCC对其进行编译得到SimpleSection.o 文件
利用objdump工具查看该目标文件的结构和内容:

除了代码段, 数据段和BSS段之外, 还有只读数据段.rodata, 注释信息段.comment和堆栈提示段.note.GNU-stack.
利用图表示如下:

(1) 代码段
利用objdump的-s选项可以将所有段的内容以十六进制的形式打印出来, 利用-d选项可以将指令进行反汇编.
(2) 数据段和只读数据段

(3) BSS段

注意点: 变量存放位置

5. ELF文件格式描述
1. ELF文件的格式如下:


(1) 文件头
可以使用readelf命令来详细查看ELF文件


ELF头文件结构及相关的常数定义在"/usr/include/elf.h" 文件中. 因为ELF文件在各种平台上都通用, 所以,ELF有32位和64位版本, 头文件结构也分为两个版本: Elf32_Ehdr和Elf64_Ehdr. 下面是32位下的定义:
#define EI_NIDENT (16)
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;
下面是头结构体成员的含义


ELF魔数


(2)段表
利用readelf –S查看段表如下:


段表的结构如下:
typedef struct
{
Elf32_Word sh_name; /* Section name (string tbl index) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;
下图是SimpleSection.o中段表及所有段的位置和长度

(3) 重定位表

(4) 字符串表



6. 链接的接口-符号
(1) ELF符号表结构
typedef struct
{
Elf32_Word st_name; /* Symbol name (string tbl index) */
Elf32_Addr st_value; /* Symbol value */
Elf32_Word st_size; /* Symbol size */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf32_Section st_shndx; /* Section index */
} Elf32_Sym;
成员定义如下:

利用readelf命令查看SimpleSection.o的段表如下:

几乎和Elf32_Sym一一对应.
7. C++函数签名
GCC采取的方式

Visual C++采取的方式

8. 弱符号与强符号


第4章 静态链接
第1节 空间与地址分配
将两个目标文件进行链接成一个文件时, 可以采取按序叠加的方式, 这种方式比较浪费空间, 并且比较零散. 一种比较好的方法就是将相似的段合并到一起.


利用objdump查看目标文件链接前后的变化:



第2节 符号解析与重定位
1. 链接前与链接后的对比


2. 重定位表

3. 符号解析
重定位过程伴随着符号解析的过程
4. 指令修正方式

5. COMMON块

第4节 C++相关问题
(1) 重复代码消除


(2) 全局构造与析构

第5节 静态库链接






第6节 链接过程控制
1. 链接控制脚本


第5章 Windows PE/COFF

1. COFF文件结构


2. Windows 下的ELF-PE



浙公网安备 33010602011771号