程序员的自我修养

  • ELF文件格式与windows PE/COFF章节未做细致分析

review

操作系统:硬件资源管理,抽象接口
同一个系统:页大小固定
fork并不复制原任务内存空间,而是和原任务共享一个写时复制的内存空间
写时复制:两个任务可同事自由读取内存,任意任务视图修改时,复制一份原内存内容给修改方,以免影响其他任务。
i++;汇编变为三个指令,非原子操作,会被多线程打断。
读写锁:自由态 共享/独占均可,共享态 共享成功/独占失败 ,独占态 共享失败/独占成功
volatile:直接存取原始内存地址
(两者均可能造成线程执行顺序错误)
1、阻止编译器为了提高速度将一个变量缓存到寄存器而不写回;
2、阻止编译器调整操作volatile变量的顺序
barrier指令:阻止编译器将barrier之前的指令换到barrier之后

预编译 gcc -E
以“#”开头语句被处理,宏展开,注释清楚,
保留#prama给编译器,插入行号和文件名表示用于编译错误提醒
编译 gcc -S
生成汇编代码
gcc 根据不同参数,调用预编译程序,汇编器,链接器
汇编
as/gcc -c
链接:地址空间分配,符号决议(绑定),重定位
编译时不能确定地址,链接时确定和修改地址:重定位

object file

目标文件、动态链接库、静态链接库、可执行文件均按照可执行文件格式存储,linux中为ELF
ELF文件分类
1、可重定位文件relocatable file linux.o
2、可执行文件 executable file windows.exe
3、共享目标文件shared object file linux.so windows.dll
4、核心转储文件 coredump file linux.core dump(意外终止进程文件内容与信息)
c语言编译后文件:
.text 执行语句
.data 以初始化的全局变量和局部静态变量
.bss 未初始化的全局变量和局部静态变量

objdump -x -s -d name.o
文件name.o内容显示格式:
.字段名 十六进制存储标识 汇编语言

自定义段存储位置
attribute((section(“name”))) int var=value;
将var放置在name段中
readlf -h name.o显示ELF文件

链接器为目标文件分配地址和空间:
1、分配输出文件空间
2、完整装载厚厚的地址定位,即形成程序虚拟地址
two-passing link
1、空间域地址分配:文件、符号合并,建立映射关系
2、符号解析与重定位:调整代码中地址为虚拟地址
可重定位文件中,每一个可重定位ELF段赌赢一个重定位表
objdump -r name.o 查看name.o的重定位表
重定位过程中:每个重定位入口都是对一个符号的引用
readelf -s name.o 查看name.o的符号表
重定位方式:
R_386_32 1 绝对寻址修正S+A
R_386_PC32 2 相对寻址修正S+A-P
A=保存在被修正位置的值
P=被修正的位置(相对于断开始的偏移量或者虚拟地址)
S=符号实际地址r_info(重定位表)确定的符号的实际地址

弱符号的COMMON块机制
原因:编译器允许不同类型的弱符号存在,本质时链接器部支持符号类型,即无法判断符号类型是否一致
弱符号:未初始化的全局变量定义
两个弱符号以大的为准
一强一弱以强为准,若果若大小大于强,报错
gcc -fno-common/int global atttribute((nocommon))
定义未初始化全局变量nocommon不以COMMON块形式处理
一个为初始化的全局变量不以COMMON块形式存在,就相当于一个强符号,如果其他目标文件还存在同一个变量的强符号定义,连接时发生符号重定义
c++中编译问题
1、重复代码
模板在不同文件被相同类型实例化造成代码重复
解决方案:将每个模板的市里代码单独放置在一个段里,每个段只把含有一个模板实例
有虚函数的类有一个虚函数表,在不同编译单元生虚函数表造成代码重复,外部内联函数、默认构造函数、默认拷贝函数、赋值操作和虚函数表做法类似
函数级别链接:每个函数单独成段,单独链接一个函数段,链接时必须明确函数依赖关系
全局构造与析构
c++的全局构造函数在mian函数之前执行,析构函数在main之后执行
linux中程序入口为_start,这个函数就是程序初始化入口,初始化完成后调用main函数主体,main完整后返回到初始化部分,清理现场,结束进程
ABI:可执行二进制兼容性相关内容(符号修饰标准,变量内存布局,函数调用方式等)二进制层面接口
静态库:一组目标文件object的集合

execute file loading and process

装载:程序局部性,覆盖装入overlay、页映射paging
进程的建立
1、创建独立虚拟地址空间\
2、读取可执行文件头,建立虚拟空间与可执行文件的映射关系
& 虚拟空间发生页错误时定位可执行文件位置
3、CPU指令寄存器设置成可执行文件入口地址
&涉及用户态与内核态转换
进程虚拟地址空间VMA:
代码VMA:只读,可执行
数据VMA:可读可写可执行
堆VMA: 可读写可执行
栈VMA: 可读写不可执行

gcc -fPIC -shared -o lib.s0 lib.c 生成动态库
静态共享库 :操作系统划分固定空间存储静态库模块
动态共享库:共享对象编译时不明却自己的进程虚拟地址空间中的位置
PIC地址无关代码:
模块内部调用与跳转:相对寻址
模块内数据访问:相对寻址
模块间数据访问:全局偏移表,标记偏移位置
模块见调用域跳转:全局偏移表保存目标函数位置
延迟绑定:函数第一次调用时绑定
/lib 关键基础共享库
/usr/lib 开发共享库
/usr/local/lib 三方应用库

memory

内核空间,栈向下延伸,动态链接库在堆栈中间,堆向上延伸,读写部分(.data .bss),只读部分(.init .text .rodata)装载起始位置0x08048000,之下为reserved
segment fault:指针非法,读写不可读写空间,指针未初始化或者初始化为NULL就使用指针
堆栈帧:储存函数返回地址与参数,临时变量,函数调用前后不便的寄存器(上下文)
esp指向栈顶,ebp指向活动栈帧,又称为帧指针
ebp函数返回位置
调用惯例calling conversion
函数参数传递顺序域方式:压栈传递参数.函数调用方压栈,函数本身弹栈取值
c语言调用惯例:从右至左压栈参数
返回值传递:使用临时栈上存储区作为中转,两次copy,一次存入栈上tmp,一次存回返回区

堆空间由运行库管理
mmap申请匿名空间实现malloc
堆分配算法:空闲链表\位图\对象池

MINI CRT的实现