3章 目标文件
1. Executable (File)
Executable = COFF (Common File Format)
Windows: PE (Portable Executable)
Linux : ELF (Executable Linkable Format)
Executable File
Windows: PE 文件
Linux : ELF 文件
——————————————————————————————————————————————————————————————————————————————————————————
Executable File 类型| 说明 | Windows / Linux 下 实例
——————————————————————————————————————————————————————————————————————————————————————————
可重定位文件 | 含 code + data | .obj / .o
Relocatable File | 可 链接 成 可执行文件 或 共享目标文件 |
| 代表: 静态链接库 |
——————————————————————————————————————————————————————————————————————————————————————————
可执行文件 | 可 直接执行 | .exe / bash文件 / .out
Executable File | 代表: ELF 文件 |
——————————————————————————————————————————————————————————————————————————————————————————
共享目标文件 | 含 code + data |
Shared Object File | 2种用途 | DLL / .so
| 1) 多个... + 可重定位文件 -> 链接器 |
| -> 新目标文件 |
| |
| 2) 多个... + 可执行文件 -> 动态链接器 |
| -> 作 进程映像 的一部分 运行 |
——————————————————————————————————————————————————————————————————————————————————————————
核心转储文件 | 进程 意外终止 -> OS 将 进程的 | Linux 下的 core dump
Core Dump File | `地址空间内容 + 其他信息` 转储到 核...件 |
——————————————————————————————————————————————————————————————————————————————————————————
2. 目标文件 content
2.1 含 link 所需信息: 符号表、字符串 等
(1) File Header
文件属性
是否可执行
静/动 态链接
入口地址 (若为 可执行文件)
目标硬件
目标 OS
段表 (Section Table)
是 array, 每个 elem 是 `段描述`
在 Object/Executable File 中的 偏移
段属性
(2) 代码段 (Code Section)
.code .text
local non-static var
(3) 数据段 (Data Section)
.data
(4) BSS 段
.bss 段
2.2 分离 code 与 data
多线程/多进程, codePart 相同, memory 中只存1份 codePart
3. SimpleSection.o
(1) 代码段
$ objdump -s -d SimpleSection.o
-s
段 `内容` 以 十六进制 打印
-d
`指令段 反汇编`
(2) 数据段 和 只读数据段
$ objdump -x -s -d SimpleSection.o
.data 段
已初始化 的 global var / local static var
.rodata 段
1) 只读变量 (const var)
2) 字符串常量 -> 有的 Compiler 将其放 .data 段
作用
1] 语义上 支持 C++ const 关键字
2] OS 加载时, 可将 .rodata 映射成 `只读` -> 阻止 非法操作 -> 安全
3] 嵌入式 -> ROM
(3) BSS 段: 预留 空间, link 时才真正分配
global var / local static var
1] init -> 放 .data 段 -> 初始化 为 0 -> 优化到 .bss 段
2] uninit -> `COMMON 块` -> 链接 时, 再在 .bss 段 分配空间
static: 仅 编译单元 (.o) 内可见
(4) 自定义段
func / global var 前加 __attribute__( (section("sectionName") ) )
// -c: 只编译 不链接
$ gcc -c SimpleSection.c
|
| 工具 objdumo 查 object 内部结构
|/
$ objdump -h SimpleSection.o
-h
1) 段 索引 / name / 长度 (Size) / 位置偏移 (File Offset)
=> .o 的 分布结构
2) 段属性
$ size SimpleSection.o
查 代码段 数据段 BSS 段 的 长度
4. ELF 文件结构
(1) 文件头: ELF Header
// 查看 文件头
$ readelf -h SimpleSection.o
描述文件的 基本属性
Elf32_Ehdr
————————————————————————————————————————————————————————————————————————
1) type
4 种
2) machine
elf 文件的 CPU 平台
3) entry
elf 程序的 `入口虚拟地址`: OS 加载完 程序后, 从该 地址开始 `执行 进程指令`
可重定位文件 无 入口地址
4) shoff
段表在文件中的偏移
————————————————————————————————————————————————————————————————————————
(2) 段表 (Section Header Table)
编译器 链接器 装载器 据 段表 来 `定位 和 访问` 各段的属性
是 array
elem 是 `段描述符结构 Elf32_Shdr`
|
|
|
|
——————————————————————————————————————
段名
段类型
SHT_NULL 无效
SHT_PROGBITS 程序段: .code .data
SHT_SYMTAB `符号表`
SHT_STRTAB `字符串表`
SHT_RELA `重定位表`
段偏移
段长度
——————————————————————————————————————
(3) 重定位表 .rel.text
`代码段 和 数据段` 中 对 `绝对地址 的 引用` 处 -> 重定位
printf 的调用
(4) 字符串表
段名 字符串常量
|
|
字符串: 长度不定
|
|
存到 `表`, 用 `字符串 在表中的 (首字符)偏移` 来引用- (均以 '\0' 结尾)
——————————————————————————————————————
.strtab 字符串表 普通字符串
.shstrtab 段表字符串表 段名
——————————————————————————————————————
5. 链接 的 接口 —— 符号
`目标文件` 间对 `符号 (func / var)地址 的 引用`
|
| 1 个 .o 1 个 符号表
|/
符号表 (Symbol Table)
目标文件(.o) 符号表 中 符号分类: 4 类
————————————————————————————————————————————————————————
`定义的 global symbol` | 可被 other 目标文件 引用
| func1 / main / global_init_var
————————————————————————————————————————————————————————
`引用的 global symbol` |
| 称: 外部符号 (External Symbol)
| printf
————————————————————————————————————————————————————————
local symbol: | 只在 本编译单元 (.o) 内部可见
| static_var / static_var2
| linker 忽略之
————————————————————————————————————————————————————————
段名 | compiler 产生, 值 = 段起始地址
————————————————————————————————————————————————————————
(1) ELF 符号表 结构 Elf32_Sym
$ readelf -s SimpleSection.o
——————————————————————————————————————————————————————————————————————————————————————————————
name 符号名
——————————————————————————————————————————————————————————————————————————————————————————————
size 符号大小 | 含 数据 的 符号 数据类型 的大小
——————————————————————————————————————————————————————————————————————————————————————————————
符号类型 | .._OBJECT 数据对象: 变量、数组 等
Symbol Type | .._FUNC 函数、其他可执行代码
info ————————————————————————————————————————————————————————————————————————————————————————
| ..LOCAL 局部符号, 目标文件外部不可见
符号绑定信息 | .._GLOBAL 全局符号, 外部可见
Symbol Binding| .._WEAK 弱引用
——————————————————————————————————————————————————————————————————————————————————————————————
shndx 符号所在段 | .._ABS 值 0xfff1 绝对值, 如 表示文件名的符号
| .._`COMMON` `uninit global` symbol
| .._UNDEF 本目标文件中引用 而非定义
——————————————————————————————————————————————————————————————————————————————————————————————
value 符号值 | 符号定义 + 非 "COMMON块" : 符号在 `段中偏移`
| func1 / main / global_init_var
| 目标文件 ——————————————————————————————————————————————————————————
| "COMMON块" : 符号的对齐属性
| ————————————————————————————————————————————————————————————————————-——
| 可执行文件 : 符号虚拟地址,
| 动态链接器 用
——————————————————————————————————————————————————————————————————————————————————————————————
(2) C++ 符号修饰
与 函数签名
不同/同一 语言间 目标文件 相互引用
|
|/
符号冲突
|
|/
C 语言: 符号名前统一加 下划线 "_"
|
|/
C++ 引入 `名字空间 namespace`
|
| C++ 类 / 继承 / 虚机制 / 重载 / namespace 等 特性
| => 符号管理 更复杂
|/
`C++ 符号修饰`
1) 函数重载
最简情形: funcName 相同、paraList 不同
2) 不同 namespace、相同 name
|
| `函数签名 -> 符号修饰` -> 修饰后符号
| |
| |/
|
| funcName + paraType + 所在 Class / namespace
|/
——————————————————————————————————————
函数签名 修饰后符号
——————————————————————————————————————
int func(int) _Z4funci
float func(float) _Z4funcf
int C::func(int) _ZN1C4funcEi
int N::C::func(int) _ZN1N1C4funcEi
——————————————————————————————————————
int N::C::func(int)
_Z N 1N 1C 4func E i
| | | | | |
1) nest 嵌套 3) 名字前 加 数字:名字长度 3) end paraList 类型缩写
解析 修饰后的名称: binutils 中 c++filt 工具
$ c++filt _ZN1N1C4funcEi
N::C::func(int)
(3) extern "C"
C++ 编译器 将 extern "C" 括号内代码 当 C 代码处理, C++ 的名称修饰 不起作用
|
| 但 C 不支持 extern "C" 语法, 为 解决 C / C++ 兼容 extern "C"
|/
C++ `宏 __cplusplus`
C++ 编译器 编译 C++ 程序时, 默认定义 该宏, 而 C 编译器 不定义
#ifdef __cplusplus
extern "C" {
#endif
// 若不用 extern, `C++ 编译器 之 符号修饰 -> -Z6memsetPvii`
// -> 链接器 与 C 库中 `_memset 符号 链接 -> symbol undefined
void* memset(void*, int, size_t);
#ifdef __cplusplus
}
#endif
(4) 强/弱 符号
1) `多目标文件 & 符号重复定义`
2) 强/弱 符号 | 引用 : 是 符号 `定义 | 引用`
强 符号/引用
|
| __attribute__( (weak / weakref) ) 可 转换
|/
弱 符号/引用
3)
————————————————————————————————————————————————————
编译器 怎么区别 强/弱 符号 ?
————————————————————————————————————————————————————
func / globalInitVar 定义 -> compiler 默认为 强符号
————————————————————————————————————————————————————
globalUninitVar 定义 -> 弱符号
————————————————————————————————————————————————————
4)
———————————————————————————————————————————————————————————————————————————————
| 链接器 如何 区别 ? | 链接器 如何处理 ?
———————————————————————————————————————————————————————————————————————————————
强 引用 | 必须 正确 决议 | 没找到 符号定义, 报错 `符号未定义 Symbol Undefind`
———————————————————————————————————————————————————————————————————————————————
弱 引用 | 不必 明确决议 | 找到 符号定义 -> 用
| 链接器 可给 默认值 | `没找到定义 -> 不报错, 默认其为 0` 或 特殊值
———————————————————————————————————————————————————————————————————————————————
5) 弱符号/引用 应用
1) 库 的链接
库中定义的 `弱符号`, 可被 `用户定义 的 强符号 覆盖`
=> 程序支持 `自定义库函数`
用户自定义 printf 函数
2) `扩展功能模块 的 引用` -> 声明为 -> `弱引用`
=> 易 裁剪 和 组合
3) `弱引用` 修饰 pthread_create
=> 据 `编译时 是否有 -lphread 选项`,
在 `运行时 判断` 是否 `链接到 pthread 库 (=> 是否 能找到 pthread_create 的定义)`
=> 从而决定 执行 多线程版本 还是 单线程版本
#include <stdio.h>
#include <pthread.h>
int pthread_creat(
pthread_t*,
const pthread_attr_t*,
void* (*)(void*),
void*) __attribute( (weakref) );
int main()
{
if(pthread_creat)
{
printf("multi-thread version\n");
}
else // 单线程编译 => 找不到 pthread_creat 定义 => 默认为 0
{
printf("single-thread version\n");
}
}
c 程序 映射到 目标文件.jpg
.o 结构.jpg
网友评论