ELF 可执行文件的装载与执行
程序的启动过程
案例代码:
package main
import "time"
var a = 300
var b = 300
func main() {
for {
time.Sleep(time.Second * 10)
}
}我们将代码传到Linux服务器,然后对这段代码进行编译:go build demo1.go
我们使用echo $$来查看当前Linux的控制进程,也就是bash进程或者sh进程。
[root@VM-16-4-centos base]# echo $$
11885编译后查看文件类型是一个静态链接。
[root@VM-16-4-centos base]# file demo1
demo1: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped使用strace命令来跟踪控制进程
strace -f -s 65000 -i -t -e trace=read,clone,fork,execve,wait4 -p 11885
控制进程调用
read函数接收./demo1数据控制进程调用
clone函数创建一个进程并分配好虚拟地址空间新子进程调用
execve加载demo1程序【ELF 可执行文件,程序指令 + 程序数据】加载到内存中去执行加载时,它并不是全部加载所有的指令和数据,它在加载时,操作系统会先把进程的虚拟地址空间按页分割,同时也会把物理内存也按页分割,也把磁盘按页分割。
- 创建好虚拟地址空间
- 读取 ELF 可执行文件信息,并且建立映射关系【把 ELF 文件的 VMA 和进程的虚拟地址空间建立映射关系】
- 找到可执行程序的入口地址,开始执行
使用pstree -ap来查看进程父子关系
├─sshd,1112 -D
│ ├─sshd,11857
│ │ └─bash,11885
│ │ └─demo1,15602
│ │ ├─{demo1},15603
│ │ ├─{demo1},15604
│ │ └─{demo1},15605cd /proc/15602然后查看进程的虚拟地址空间【进程内存布局】
[root@VM-16-4-centos 15602]# cat maps
地址范围 权限 dev node 所映射的文件
00400000-00458000 r-xp 00000000 fd:01 797257 /data/work/gopath/src/te/base/demo1 # 映射的文件 代码段 VMA代码段
00458000-004bf000 r--p 00058000 fd:01 797257 /data/work/gopath/src/te/base/demo1 # 只读数据段 VMA只读
004bf000-004c4000 rw-p 000bf000 fd:01 797257 /data/work/gopath/src/te/base/demo1 # 读写数据段 VMA读写
004c4000-004f7000 rw-p 00000000 00:00 0
c000000000-c000400000 rw-p 00000000 00:00 0
c000400000-c004000000 ---p 00000000 00:00 0
7fb351003000-7fb353374000 rw-p 00000000 00:00 0
7fb353374000-7fb3634f4000 ---p 00000000 00:00 0
7fb3634f4000-7fb3634f5000 rw-p 00000000 00:00 0
7fb3634f5000-7fb3753a4000 ---p 00000000 00:00 0
7fb3753a4000-7fb3753a5000 rw-p 00000000 00:00 0
7fb3753a5000-7fb37777a000 ---p 00000000 00:00 0
7fb37777a000-7fb37777b000 rw-p 00000000 00:00 0
7fb37777b000-7fb377bf4000 ---p 00000000 00:00 0
7fb377bf4000-7fb377bf5000 rw-p 00000000 00:00 0
7fb377bf5000-7fb377c74000 ---p 00000000 00:00 0
7fb377c74000-7fb377cd4000 rw-p 00000000 00:00 0
7fff1538f000-7fff153b0000 rw-p 00000000 00:00 0 [stack] # VMA 栈
7fff153f9000-7fff153fb000 r-xp 00000000 00:00 0 [vdso] # 内核
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] # 系统调用VMA地址范围:
- r: read
- w: write
- e:execute
- p:private
- s: 共享 share
可以使用man proc去查看文档,然后输入/maps查找

使用readelf -l demo1来查看程序头表
[root@VM-16-4-centos base]# readelf -l demo1
Elf file type is EXEC (Executable file)
Entry point 0x454b20
There are 7 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x0000000000000188 0x0000000000000188 R 1000
NOTE 0x0000000000000f9c 0x0000000000400f9c 0x0000000000400f9c
0x0000000000000064 0x0000000000000064 R 4
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000576ce 0x00000000000576ce R E 1000
LOAD 0x0000000000058000 0x0000000000458000 0x0000000000458000
0x0000000000066b20 0x0000000000066b20 R 1000
LOAD 0x00000000000bf000 0x00000000004bf000 0x00000000004bf000
0x0000000000004020 0x0000000000037fa0 RW 1000
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 8
LOOS+5041580 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 8
Section to Segment mapping:
Segment Sections...
00
01 .note.go.buildid
02 .text .note.go.buildid
03 .rodata .typelink .itablink .gosymtab .gopclntab
04 .go.buildinfo .noptrdata .data .bss .noptrbss
05
06进程的内存布局(静态链接暂时没有堆)
当程序启动时,会创建一个虚拟地址空间【进程是有自己的虚拟地址空间】同时加载 ELF 文件并读取该文件信息,同时建立映射关系(/proc/PID/maps)
程序要运行,肯定是要把程序指令和程序数据加载到内存中才可以执行的
我们的物理内存是有限的
操作系统启动的进程数量是非常多的
我们的进程使用的内存大部分情况下是会超出物理内存的
- 物理地址
- 虚拟地址
代码段
程序源码
package main
import (
"fmt"
)
var a = 300
var b = 300
func main() {
fmt.Println(a, b)
fmt.Println(&a, &b)
}程序指令,只复制 2 行出来解析,太多了,使用objdump -s demo1 > demo1.txt即可生成
401000 493b6610 76384883 ec184889 6c241048 I;f.v8H...H.l$.H
401010 8d6c2410 48894424 2048895c 24286690 .l$.H.D$ H.\$(f.401000go 编译器给可执行文件分配的虚拟地址【地址偏移】493b6610 76384883 ec184889 6c241048是指令内容指令大小:一堆 4 个字节,总共 16 个字节
指令对应的汇编语句:
bash[root@VM-16-4-centos base]# size demo1 text data bss dec hex filename 1093520 97776 213608 1404904 156fe8 demo1大小:1093520KB,太大,不太方便看。
可以使用
bash[root@VM-16-4-centos base]# objdump -d demo1 > demo1go3.txt来查看汇编语句
401000: 49 3b 66 10 cmp 0x10(%r14),%rsp 401004: 76 38 jbe 40103e <internal/cpu.Initialize+0x3e> 401006: 48 83 ec 18 sub $0x18,%rsp 40100a: 48 89 6c 24 10 mov %rbp,0x10(%rsp) 40100f: 48 8d 6c 24 10 lea 0x10(%rsp),%rbp 401014: 48 89 44 24 20 mov %rax,0x20(%rsp) 401019: 48 89 5c 24 28 mov %rbx,0x28(%rsp) 40101e: 66 90 xchg %ax,%ax 401020: e8 7b 06 00 00 callq 4016a0 <internal/cpu.doinit> 401025: 48 8b 44 24 20 mov 0x20(%rsp),%rax 40102a: 48 8b 5c 24 28 mov 0x28(%rsp),%rbx 40102f: e8 2c 00 00 00 callq 401060 <internal/cpu.processOptions> 401034: 48 8b 6c 24 10 mov 0x10(%rsp),%rbp 401039: 48 83 c4 18 add $0x18,%rsp 40103d: c3 retq 40103e: 48 89 44 24 08 mov %rax,0x8(%rsp) 401043: 48 89 5c 24 10 mov %rbx,0x10(%rsp) 401048: e8 b3 7b 05 00 callq 458c00 <runtime.morestack_noctxt.abi0> 40104d: 48 8b 44 24 08 mov 0x8(%rsp),%rax 401052: 48 8b 5c 24 10 mov 0x10(%rsp),%rbx 401057: eb a7 jmp 401000 <internal/cpu.Initialize> 401059: cc int3 40105a: cc int3 40105b: cc int3 40105c: cc int3 40105d: cc int3 40105e: cc int3 40105f: cc int3虚拟地址:401000
指令内容:49 3b 66 10
指令大小:4 字节
指令对应的汇编指令:
cmp 0x10(%r14),%rsp
go程序语句时多条一条
go语句:对应多条汇编语句一条汇编语句:对应多个字节的指令
每条指令都有一个虚拟内存地址
查看程序入口地址
[root@VM-16-4-centos base]# readelf -h demo1
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
版本: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: EXEC (可执行文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x45c220
程序头起点: 64 (bytes into file)
Start of section headers: 456 (bytes into file)
标志: 0x0
本头的大小: 64 (字节)
程序头大小: 56 (字节)
Number of program headers: 7
节头大小: 64 (字节)
节头数量: 23
字符串表索引节头: 3