作者:俞甲子 石凡 潘爱民
计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决
编译器:MSVC GCC LLVM | 应用程序
运行库:CRT
操作系统:Windows Linux Unix Solaris——API
硬件平台:x86 MIPS ARM SPARC
构建Build:编译+链接 or 预处理->编译->汇编->目标文件->链接->可执行文件
编译
MSVC GCC:源程序->扫描器/词法分析器Lex/有限状态机FSM->Token->语法分析器Yacc/上下文无关文法->抽象语法树AST->语义分析->中间代码表示IR->优化->代码生成->汇编文件->目标文件
LLVM:LLVM IR->优化->SelectionDAG->合法化->优化->指令选择->MachineDAG->优化->指令调度->MI->优化->寄存器分配->MCInst->代码发射/生成->汇编文件/目标文件
目标文件/可执行文件/共享对象/共享库/动态链接库/静态库的格式
Windows:PE/COFF——.obj .exe .dll .lib
Linux:ELF——.o .elf .so .a
链接
跨包/模块/目标文件的符号(函数和变量)调用
功能:地址和空间分配+符号解析和重定位
装载
Section->Segment
堆栈传入:参数个数argc+参数argv**+环境变量env+其他辅助信息
ELF
ELF Header文件头:魔数,程序入口及其地址,硬件平台及其版本
.text代码段
.data数据段
.rodata只读数据
.bss全局未初始化数据/初始化为0的数据
静态链接相关
.strtab字符串表
.symtab符号表
.shstrtab段名表
动态链接相关
.interp:存放动态链接器路径
.dynamic:存放动态链接所需要的字符表/段名表/符号表/重定位表等地址,相当于动态链接下的文件头
.got.plt:导入符号/函数引用地址
.got:导入符号/全局变量引用地址
.plt:存放PLT结构,本身是地址无关代码
.dynsym:导出的表,动态符号表
.dynstr:动态字符串表
.hash:符号哈希表,辅助动态查找符号
重定位相关
.rel.text
.rel.data
.rel.dyn:需要导入的符号,针对数据引用的修正,即.got+.data
.rel.plt:需要导入的符号,针对函数引用的修正,即.got.plt
main前后执行相关
.init
.fini
.ctors全局构造函数指针
.dtors
其他
.comment
.debug
.line
.note
PE/COFF
Image DOS Header[兼容DOS而设计]
Image DOS Stub[兼容DOS而设计]
PE File Header:PE头文件(DataDirectory(导出地址表EAT+符号名表+名字序号对应表+导入表)+Image Header:映像头文件(Image Optional Header:PE扩展头部文件))
.Section Table
.text
.data
.drectve:链接指示文件
......
Linux动态链接:共享对象-插件实现
.c->compiler->.o+CRT->linker->.so+.o->linker->.so/可执行文件
动态链接器本质也是一个共享对象/动态链接库:动态链接器自举Bootstrap,不依赖任何共享对象而启动
方式一:装载时重定位(+运行时装载=运行时重定位 | 链接时重定位->基址重置)
方式二:地址无关代码PIC:.text中需要重定位的指令和数据放在一起(模块内外部的数据访问和指令跳转条用四种情况:PC+偏移 or 直接或间接的GOT)
优化:延迟绑定PLT:符号引用到时才开始绑定(解析+重定位:需要用到.got(全局变量引用地址)和.got.plt(函数引用地址))
显式运行时加载/链接:程序员手动动态链接(dlopen打开动态库+dlsym查找符号+dlerror符号处理/类型判断+dlclose)
共享对象管理:libname.so.主版本号(互不兼容).y次版本号(可兼容,增加接口).z发布版本号(bug修复)
SO-NAME(保留最新主版本号共享对象)+基于符号版本机制(符号修饰增加yz标志,保证yz向后兼容)
/lib运行时的关键的共享库+/usr/lib非运行时的关键共享库+usr/local/bin第三方库
Windows动态链接
.c->compiler->.dll&.exp(第一步扫描产生的临时文件)&.lib&.obj | .lib+.obj->linker->.exe
显式运行时加载/链接:LoadLibrary+GetProcAddress+FreeLibrary
符号导入导出表——ELF的.rel.dyn+.rel.plt+.got+.got.plt+.dynsym
延迟载入——ELF延迟绑定
优化:重定基地址+序号取代符号名导入导出+DLL绑定(把导出表地址保存到导入表中)
内存
栈:参数+返回地址+ebp+寄存器+局部变量+esp
push ebp; mov ebp, esp; sub esp, X; push Y; ... pop Y; mov esp, ebp; pop ebp; ret
调用约定(cdecl/stdcall/fastcall/pascal):参数传递(从右到左/从左到右)+出栈方(函数调用方/函数本身)+名字修饰(前面加下划线/...)+返回值(eax+edx)
堆:程序向操作系统申请空间(Linux:brk/mmap Windows:VirtualAlloc)+用户向程序申请空间(malloc/new/HeapCreate/HeapAlloc/HeapFree/HeapDestroy)
堆分配算法:空闲链表+位图+内存池
运行库
CRT:启动与退出(全局构造和析构)+标准函数(数据运算/字符(串)操作)+IO函数+堆函数+调式函数
入口函数(Glibc:__libc_start_main->main->exit->hlt | MSVC CRT:mainCRTStartup)
程序初始化:IO初始化(键盘/屏幕)+堆初始化
MSVC静态库命名规则libc[p][mt][d].lib:是否C++/多线程库/调式版本
fread->fread_s(缓冲基础保护+加锁)->fread_nolock_s(循环读取+缓冲)->_read(换行符转换)->ReadFile(Windows API)
系统调用和API
int 0x80 + eax = 2 ->中断号0x80->中断向量表->中断处理程序->系统调用号2->系统调用表->系统调用
Windows:应用程序->CRT->API(Windows)->内核
Linux:应用程序->CRT->内核