Linux程序内存布局:
启动程序的默认加载点是在链接阶段才添加的,通常放在程序内存映射的起始处(这是可执行文件和动态库之间的唯一区别)。启动代码有两种不同方式:
root@DESKTOP-5F95GBN:~/code# objdump -D tmp.o tmp.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <main>: 0: f3 0f 1e fa endbr64 4: 55 push %rbp 5: 48 89 e5 mov %rsp,%rbp 8: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # f <main+0xf> f: b8 00 00 00 00 mov $0x0,%eax 14: e8 00 00 00 00 callq 19 <main+0x19> 19: b8 00 00 00 00 mov $0x0,%eax 1e: 5d pop %rbp 1f: c3 retq Disassembly of section .rodata: 0000000000000000 <.rodata>: 0: 68 65 6c 6c 6f pushq $0x6f6c6c65 5: 20 77 6f and %dh,0x6f(%rdi) 8: 72 6c jb 76 <main+0x76> a: 64 fs ... Disassembly of section .comment: 0000000000000000 <.comment>: 0: 00 47 43 add %al,0x43(%rdi) 3: 43 3a 20 rex.XB cmp (%r8),%spl 6: 28 55 62 sub %dl,0x62(%rbp) 9: 75 6e jne 79 <main+0x79> b: 74 75 je 82 <main+0x82> d: 20 39 and %bh,(%rcx) f: 2e 34 2e cs xor $0x2e,%al 12: 30 2d 31 75 62 75 xor %ch,0x75627531(%rip) # 75627549 <main+0x75627549> 18: 6e outsb %ds:(%rsi),(%dx) 19: 74 75 je 90 <main+0x90> 1b: 31 7e 32 xor %edi,0x32(%rsi) 1e: 30 2e xor %ch,(%rsi) 20: 30 34 2e xor %dh,(%rsi,%rbp,1) 23: 31 29 xor %ebp,(%rcx) 25: 20 39 and %bh,(%rcx) 27: 2e 34 2e cs xor $0x2e,%al 2a: 30 00 xor %al,(%rax) Disassembly of section .note.gnu.property: 0000000000000000 <.note.gnu.property>: 0: 04 00 add $0x0,%al 2: 00 00 add %al,(%rax) 4: 10 00 adc %al,(%rax) 6: 00 00 add %al,(%rax) 8: 05 00 00 00 47 add $0x47000000,%eax d: 4e 55 rex.WRX push %rbp f: 00 02 add %al,(%rdx) 11: 00 00 add %al,(%rax) 13: c0 04 00 00 rolb $0x0,(%rax,%rax,1) 17: 00 03 add %al,(%rbx) 19: 00 00 add %al,(%rax) 1b: 00 00 add %al,(%rax) 1d: 00 00 add %al,(%rax) ... Disassembly of section .eh_frame: 0000000000000000 <.eh_frame>: 0: 14 00 adc $0x0,%al 2: 00 00 add %al,(%rax) 4: 00 00 add %al,(%rax) 6: 00 00 add %al,(%rax) 8: 01 7a 52 add %edi,0x52(%rdx) b: 00 01 add %al,(%rcx) d: 78 10 js 1f <.eh_frame+0x1f> f: 01 1b add %ebx,(%rbx) 11: 0c 07 or $0x7,%al 13: 08 90 01 00 00 1c or %dl,0x1c000001(%rax) 19: 00 00 add %al,(%rax) 1b: 00 1c 00 add %bl,(%rax,%rax,1) 1e: 00 00 add %al,(%rax) 20: 00 00 add %al,(%rax) 22: 00 00 add %al,(%rax) 24: 20 00 and %al,(%rax) 26: 00 00 add %al,(%rax) 28: 00 45 0e add %al,0xe(%rbp) 2b: 10 86 02 43 0d 06 adc %al,0x60d4302(%rsi) 31: 57 push %rdi 32: 0c 07 or $0x7,%al 34: 08 00 or %al,(%rax) ...
链接器是一个高度复杂的模块,它要准确的区分出各种节的属性(代码、未初始化数据、初始化数据、构造器、调试信息等)。
装载器则要简单。它将链接器创建的节复制到内存映射中,装载器不需要了解各个节的内部结构就能完全复制。它只要关心节读写属性,以及在启动前是否需要打补丁。(涉及到动态链接时还要比复制数据块更复杂一些)装载器还会根据节的相同装载需求将链接器创建的节组合成段。装载器的段一般会携带多个拥有相同访问属性的节。
可使用 readelf 检查段
暂时只讨论静态链接场景,使用 gcc 的 -static 生成静态链接代码。
装载完成后(即准备程序基本数据和复制程序必要的节到内存中),装载器会查询ELF头的 e_entry 字段的值。这个值包含的程序内存地址指定了该程序从何处开始。e_entry 值包含了 .text 节的首地址,通常就是 _start 函数的首地址。
_start() 函数为接下来需要调用的 __libc_start_main 函数准备入参
__libc_start_main 为程序启动准备环境的过程中扮演了重要角色。启动阶段,它不仅会为程序设置好运行环境,会执行以下操作:
最后,所有准备操作完成时,__libc_start_main() 调用 main() 函数,启动程序。
函数调用过程: ELF.e_entry字段 -> _start() -> int __linc_start_main() -> main()
静态库:
动态库:
动态库的两种实现方式:
1. 创建动态库需要完整的构建过程
静态库只需要编译,动态库则需要编译和链接。这使得动态库更加类似可执行文件,唯一区别是可执行文件包含启动代码。
2. 动态库可以链接其他库
可执行文件和动态库都可以加载和链接动态库。
由软件模块提供给客户端的接口通常是应用程序编程接口(API)。当提供给客户端的是二进制文件时,接口的定义就会发生变化,成为应用程序二进制接口。可以把ABI当作编译链接过程中根据源代码接口创建的符号集合(主要是函数入口点)。
Linux 静态库 https://www.cnblogs.com/zhh567/p/16664821.html
Windows 静态库 https://www.cnblogs.com/zhh567/p/16536626.html