程序编译过程与运行时内存
原文地址:https://www.cnblogs.com/liqinglucky/p/compiler.html
1 内存
1.1 存储与类型
处理器获取数据的顺序:
-
寄存器
-
其次是高速缓存(cache),高速缓存(cache)分为L1 cache,L2 cache ,L3 cache。如果直到L3 cache才找到,就将数据从L3 cache拷贝至L2 cache,将L2 cache拷贝至L1 cache最后拷贝至寄存器。这样CPU就拿到了数据。
-
其次是主内存
-
最后是二级内存,如硬盘。
存储的类型:[1]
类型 | 容量 | CPU访问速度 | 易失性(Volatile) | 造价 |
---|---|---|---|---|
寄存器(registers) | 16/32/64 bits,depending on the type of CPU | < 10ns | 易丢失 | 昂贵 |
高速缓存(cache) | in K bytes | 10-50ns | 易丢失 | |
内存(RAM , main Memory) | in Mbytes; some GBs | 50-100ns | 易丢失 | |
磁盘(secondary storage) | in GBs and TBs | 10 millisec | 不易丢失 | 便宜 |
说明:
-
L1 cache = 32 KB and 64 B/line
L2 cache = 256 KB and 64 B/line
L3 cache = 4 MB and 64 B/line
-
内存:用于临时存放运行的程序——进程
为什么内存要分级?CPU如何访问内存?参考:[2]
所有数据都在一个存储器里,存储器的大容量必然导致访问速度变慢。毕竟CPU运行速度很快,如果读写速度变慢就拖了CPU的后腿,不能让CPU发挥最大实力。
1.2 内存排列
内存可以看成是一条位置连续的数组,每个位置都有个唯一地址,每个位置都可以存放0或1的数据[3]。内存地址是一个数字编号,用来标识信息单元。
内存模型如下:内存转储(memory dump)
内存地址 | 数据 |
---|---|
0x0001100 0x0001110 0x0001120 0x0001130 0x0001140 0x0001150 0x0001160 0x0001170 0x0001180 0x0001190 0x00011a0 |
0000 29E8 FFFF E8FF FF64 FFFF 05C6 2EFD 0000 5D01 0FC3 001F 0FC3 801F 0000 0000 0FF3 FA1E 77E9 FFFF F3FF 1E0F 55FA 8948 89E5 FC7D 7589 8BF8 FC55 458B 01F8 5DD0 F3C3 1E0F 55FA 8948 48E5 EC83 C710 F445 000A 0000 45C7 14F8 0000 BE00 0014 0000 0ABF 0000 E800 FFBF FFFF 4589 8BFC FC45 C3C9 2E66 1F0F 0084 0000 0000 1F0F 0040 0FF3 FA1E 5741 8D4C 633D 002C 4100 4956 D689 5541 8949 41F5 4154 FC89 4855 2D8D 2C54 0000 4C53 FD29 8348 08EC 4FE8 FFFE |
2 编译过程链
回答的问题:
1 为什么运行系统换了要重新编译
编译是按步骤执行的过程,每一步都输出是下一步的输入。最终的编译结果是一个可以在32位/64位操作系统平台运行的可执行文件[4]。
可执行文件的格式可分为:
-
Linux系统可识别的ELF格式(Executable and Linker Format)
-
Windows系统可识别的PE格式(Portable Executable)
Source-code ➤ Preprocessing ➤ Compilation ➤ Assembler ➤ Object file ➤ Linker ➤ Executable
源文件 ➤ 预处理 ➤ 编译 ➤ 汇编 ➤ 中间文件 ➤ 链接 ➤ 可执行文件
源文件(Source-code)
.c, .h文件
#include <stdio.h>
int add(int x, int y)
{
return x+y;
}
int main()
{
int a = 10;
int b = 20;
int c;
c = add(10,20);
return 0;
}
2.1 预处理 (Preprocessing)
预处理器(cpp)根据以#
号开头的命令,修改原始的C文件。
-
加载头文件。#include
-
拓展宏定义。#define
源程序C文件预处理后得到.i
文件:
gcc -E func.c -o func.i
# 1 "func.c"
typedef unsigned char __u_char;
typedef unsigned short int __u_short;
typedef unsigned int __u_int;
typedef unsigned long int __u_long;
extern int printf (const char *__restrict __format, ...);
# 840 "/usr/include/stdio.h" 3 4
# 3 "func.c"
int add(int x, int y)
{
return x+y;
}
int main()
{
int x = 10;
int y = 20;
int z;
z = add(10,20);
return z;
}
2.2 编译 (Compilation)
编译器(ccl)将文本文件.i
文件翻译成汇编代码(assembly code
)程序.s
文件。汇编代码里有机器语言指令符
# gcc -S func.i -o func.s
.file "func.c"
.text
.globl add
.type add, @function
add:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp <<< 汇编代码
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl -4(%rbp), %edx
movl -8(%rbp), %eax
addl %edx, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size add, .-add
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $10, -12(%rbp)
movl $20, -8(%rbp)
movl $20, %esi
movl $10, %edi
call add
movl %eax, -4(%rbp)
movl -4(%rbp), %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size main, .-main
.ident "GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
2.3 汇编器 (Assembler)
汇编器(as)将.s
文件翻译成机器语言指令(generate the object code)。
代码中还用到其他库函数的地址这一步并未解析。
# gcc -c func.s -o func.o
2.4 链接 (Linking)
链接器(ld)解析所有外部函数地址[6],如:printf
,输出一个可执行文件。
链接startup routine(libc-start.c)使操作系统可以找到可执行文件的入口。
# gcc func.o -o func
可执行文件
readelf
命令可以解析ELF格式文件。
# readelf -l func
Elf file type is DYN (Shared object file)
Entry point 0x1040
There are 13 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
0x00000000000002d8 0x00000000000002d8 R 0x8
INTERP 0x0000000000000318 0x0000000000000318 0x0000000000000318
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x00000000000005c8 0x00000000000005c8 R 0x1000
LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000
0x0000000000000205 0x0000000000000205 R E 0x1000
LOAD 0x0000000000002000 0x0000000000002000 0x0000000000002000
0x0000000000000158 0x0000000000000158 R 0x1000
LOAD 0x0000000000002df0 0x0000000000003df0 0x0000000000003df0
0x0000000000000220 0x0000000000000228 RW 0x1000
DYNAMIC 0x0000000000002e00 0x0000000000003e00 0x0000000000003e00
0x00000000000001c0 0x00000000000001c0 RW 0x8
NOTE 0x0000000000000338 0x0000000000000338 0x0000000000000338
0x0000000000000020 0x0000000000000020 R 0x8
NOTE 0x0000000000000358 0x0000000000000358 0x0000000000000358
0x0000000000000044 0x0000000000000044 R 0x4
GNU_PROPERTY 0x0000000000000338 0x0000000000000338 0x0000000000000338
0x0000000000000020 0x0000000000000020 R 0x8
GNU_EH_FRAME 0x0000000000002004 0x0000000000002004 0x0000000000002004
0x0000000000000044 0x0000000000000044 R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x0000000000002df0 0x0000000000003df0 0x0000000000003df0
0x0000000000000210 0x0000000000000210 R 0x1
[权限] [偏移量]
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn
03 .init .plt .plt.got .text .fini
04 .rodata .eh_frame_hdr .eh_frame
05 .init_array .fini_array .dynamic .got .data .bss
06 .dynamic
07 .note.gnu.property
08 .note.gnu.build-id .note.ABI-tag
09 .note.gnu.property
10 .eh_frame_hdr
11
12 .init_array .fini_array .dynamic .got
objdump
命令可以看到可执行文件的内容。程序就是有特定格式规范的二进制,类似于通信协议的编解码,只有编码与解码都遵从规范才能获得正确的信息。这些二进制经过操作系统翻译之后就是汇编代码的机器指令。这些指令在运行时会按顺序给CPU执行。
# objdump -d func
func: file format elf64-x86-64
Disassembly of section .init:
0000000000001000 <_init>:
1000: f3 0f 1e fa endbr64
1004: 48 83 ec 08 sub $0x8,%rsp
1008: 48 8b 05 d9 2f 00 00 mov 0x2fd9(%rip),%rax # 3fe8 <__gmon_start__>
100f: 48 85 c0 test %rax,%rax
1012: 74 02 je 1016 <_init+0x16>
1014: ff d0 callq *%rax
1016: 48 83 c4 08 add $0x8,%rsp
101a: c3 retq
Disassembly of section .plt:
0000000000001020 <.plt>:
1020: ff 35 a2 2f 00 00 pushq 0x2fa2(%rip) # 3fc8 <_GLOBAL_OFFSET_TABLE_+0x8>
1026: f2 ff 25 a3 2f 00 00 bnd jmpq *0x2fa3(%rip) # 3fd0 <_GLOBAL_OFFSET_TABLE_+0x10>
102d: 0f 1f 00 nopl (%rax)
Disassembly of section .plt.got:
0000000000001030 <__cxa_finalize@plt>:
1030: f3 0f 1e fa endbr64
1034: f2 ff 25 bd 2f 00 00 bnd jmpq *0x2fbd(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
103b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
Disassembly of section .text:
//1. 程序加载的入口函数
0000000000001040 <_start>:
1040: f3 0f 1e fa endbr64
1044: 31 ed xor %ebp,%ebp
1046: 49 89 d1 mov %rdx,%r9
1049: 5e pop %rsi
104a: 48 89 e2 mov %rsp,%rdx
104d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
1051: 50 push %rax
1052: 54 push %rsp
1053: 4c 8d 05 96 01 00 00 lea 0x196(%rip),%r8 # 11f0 <__libc_csu_fini>
105a: 48 8d 0d 1f 01 00 00 lea 0x11f(%rip),%rcx # 1180 <__libc_csu_init>
//1.1 调用main函数
1061: 48 8d 3d d9 00 00 00 lea 0xd9(%rip),%rdi # 1141 <main>
1068: ff 15 72 2f 00 00 callq *0x2f72(%rip) # 3fe0 <__libc_start_main@GLIBC_2.2.5>
106e: f4 hlt
106f: 90 nop
0000000000001070 <deregister_tm_clones>:
1070: 48 8d 3d 99 2f 00 00 lea 0x2f99(%rip),%rdi # 4010 <__TMC_END__>
1077: 48 8d 05 92 2f 00 00 lea 0x2f92(%rip),%rax # 4010 <__TMC_END__>
107e: 48 39 f8 cmp %rdi,%rax
1081: 74 15 je 1098 <deregister_tm_clones+0x28>
1083: 48 8b 05 4e 2f 00 00 mov 0x2f4e(%rip),%rax # 3fd8 <_ITM_deregisterTMCloneTable>
108a: 48 85 c0 test %rax,%rax
108d: 74 09 je 1098 <deregister_tm_clones+0x28>
108f: ff e0 jmpq *%rax
1091: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
1098: c3 retq
1099: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
00000000000010a0 <register_tm_clones>:
10a0: 48 8d 3d 69 2f 00 00 lea 0x2f69(%rip),%rdi # 4010 <__TMC_END__>
10a7: 48 8d 35 62 2f 00 00 lea 0x2f62(%rip),%rsi # 4010 <__TMC_END__>
10ae: 48 29 fe sub %rdi,%rsi
10b1: 48 89 f0 mov %rsi,%rax
10b4: 48 c1 ee 3f shr $0x3f,%rsi
10b8: 48 c1 f8 03 sar $0x3,%rax
10bc: 48 01 c6 add %rax,%rsi
10bf: 48 d1 fe sar %rsi
10c2: 74 14 je 10d8 <register_tm_clones+0x38>
10c4: 48 8b 05 25 2f 00 00 mov 0x2f25(%rip),%rax # 3ff0 <_ITM_registerTMCloneTable>
10cb: 48 85 c0 test %rax,%rax
10ce: 74 08 je 10d8 <register_tm_clones+0x38>
10d0: ff e0 jmpq *%rax
10d2: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
10d8: c3 retq
10d9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
00000000000010e0 <__do_global_dtors_aux>:
10e0: f3 0f 1e fa endbr64
10e4: 80 3d 25 2f 00 00 00 cmpb $0x0,0x2f25(%rip) # 4010 <__TMC_END__>
10eb: 75 2b jne 1118 <__do_global_dtors_aux+0x38>
10ed: 55 push %rbp
10ee: 48 83 3d 02 2f 00 00 cmpq $0x0,0x2f02(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
10f5: 00
10f6: 48 89 e5 mov %rsp,%rbp
10f9: 74 0c je 1107 <__do_global_dtors_aux+0x27>
10fb: 48 8b 3d 06 2f 00 00 mov 0x2f06(%rip),%rdi # 4008 <__dso_handle>
1102: e8 29 ff ff ff callq 1030 <__cxa_finalize@plt>
1107: e8 64 ff ff ff callq 1070 <deregister_tm_clones>
110c: c6 05 fd 2e 00 00 01 movb $0x1,0x2efd(%rip) # 4010 <__TMC_END__>
1113: 5d pop %rbp
1114: c3 retq
1115: 0f 1f 00 nopl (%rax)
1118: c3 retq
1119: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000001120 <frame_dummy>:
1120: f3 0f 1e fa endbr64
1124: e9 77 ff ff ff jmpq 10a0 <register_tm_clones>
//3. add函数的入口地址,与运行时内存地址对应
0000000000001129 <add>:
1129: f3 0f 1e fa endbr64
112d: 55 push %rbp
112e: 48 89 e5 mov %rsp,%rbp
1131: 89 7d fc mov %edi,-0x4(%rbp)
1134: 89 75 f8 mov %esi,-0x8(%rbp)
1137: 8b 55 fc mov -0x4(%rbp),%edx
113a: 8b 45 f8 mov -0x8(%rbp),%eax
113d: 01 d0 add %edx,%eax
113f: 5d pop %rbp
1140: c3 retq
//2. main函数的入口地址,与运行时内存地址对应
0000000000001141 <main>:
1141: f3 0f 1e fa endbr64
1145: 55 push %rbp
1146: 48 89 e5 mov %rsp,%rbp
1149: 48 83 ec 10 sub $0x10,%rsp
114d: c7 45 f4 0a 00 00 00 movl $0xa,-0xc(%rbp)
1154: c7 45 f8 14 00 00 00 movl $0x14,-0x8(%rbp)
115b: be 14 00 00 00 mov $0x14,%esi
1160: bf 0a 00 00 00 mov $0xa,%edi
1165: e8 bf ff ff ff callq 1129 <add>
116a: 89 45 fc mov %eax,-0x4(%rbp)
116d: 8b 45 fc mov -0x4(%rbp),%eax
1170: c9 leaveq
1171: c3 retq
1172: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
1179: 00 00 00
117c: 0f 1f 40 00 nopl 0x0(%rax)
0000000000001180 <__libc_csu_init>:
1180: f3 0f 1e fa endbr64
1184: 41 57 push %r15
1186: 4c 8d 3d 63 2c 00 00 lea 0x2c63(%rip),%r15 # 3df0 <__frame_dummy_init_array_entry>
118d: 41 56 push %r14
118f: 49 89 d6 mov %rdx,%r14
1192: 41 55 push %r13
1194: 49 89 f5 mov %rsi,%r13
1197: 41 54 push %r12
1199: 41 89 fc mov %edi,%r12d
119c: 55 push %rbp
119d: 48 8d 2d 54 2c 00 00 lea 0x2c54(%rip),%rbp # 3df8 <__do_global_dtors_aux_fini_array_entry>
11a4: 53 push %rbx
11a5: 4c 29 fd sub %r15,%rbp
11a8: 48 83 ec 08 sub $0x8,%rsp
11ac: e8 4f fe ff ff callq 1000 <_init>
11b1: 48 c1 fd 03 sar $0x3,%rbp
11b5: 74 1f je 11d6 <__libc_csu_init+0x56>
11b7: 31 db xor %ebx,%ebx
11b9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
11c0: 4c 89 f2 mov %r14,%rdx
11c3: 4c 89 ee mov %r13,%rsi
11c6: 44 89 e7 mov %r12d,%edi
11c9: 41 ff 14 df callq *(%r15,%rbx,8)
11cd: 48 83 c3 01 add $0x1,%rbx
11d1: 48 39 dd cmp %rbx,%rbp
11d4: 75 ea jne 11c0 <__libc_csu_init+0x40>
11d6: 48 83 c4 08 add $0x8,%rsp
11da: 5b pop %rbx
11db: 5d pop %rbp
11dc: 41 5c pop %r12
11de: 41 5d pop %r13
11e0: 41 5e pop %r14
11e2: 41 5f pop %r15
11e4: c3 retq
11e5: 66 66 2e 0f 1f 84 00 data16 nopw %cs:0x0(%rax,%rax,1)
11ec: 00 00 00 00
00000000000011f0 <__libc_csu_fini>:
11f0: f3 0f 1e fa endbr64
11f4: c3 retq
Disassembly of section .fini:
00000000000011f8 <_fini>:
11f8: f3 0f 1e fa endbr64
11fc: 48 83 ec 08 sub $0x8,%rsp
1200: 48 83 c4 08 add $0x8,%rsp
1204: c3 retq
2.5 加载器 (Loader)
回答的问题:
1 数据是怎么翻译进内存的。函数的内存地址是怎么得到的。
程序运行时,操作系统的加载器将可执行文件加载至内存[6:1]
# ./func
加载器 (Loader)不是编译过程,是操作系统将可执行文件加载进内存这个过程。加载器 (Loader)完成的工作:
- 做校验(Validation )
- 拷贝可执行程序的代码这些文本内容至主内存
- 创建栈
- 创建寄存器
- 跳转到进入程序起始入口 (_start)
操作系统会创建一个进程将可执行文件加载至内存。可执行文件里包含了指令和数据,加载到内存后每条代码指令就有了对应的内存地址。进程的内存结构在下一章会讲到。编译好后,函数地址在可执行文件里就确定了。同样加载到内存后函数地址也就确定了。
加载器跳转到程序的入口点(_start
函数的地址)。该函数初始化执行环境,调用用户层的main
函数。程序就运行起来了。
动态库的加载
动态链接库并不在链接时将需要的二进制代码都“拷贝”到可执行文件中,而是仅仅“拷贝”一些重定位和符号表信息。也就是可执行文件里只有一个“指向”动态库文件的位置信息而已。动态库文件在linux中通常以.so(shared object)作为后缀。在运行时(runtime)才会将动态库加载进内存,完成真正的链接过程。
动态库是一个整体,其中的模块都被合成到一起,不可分割了,加载的时候是整个库一起被加载。但是并不意味着把整个库全部读入内存。动态链接库都是用mmap文件映射的方法映射到内存的。只有实际访问到的区域才会读入内存[7]。静态库中的模块是分离的,可以分别提取出来合成到可执行文件中。
mmap
mmap 的全称是 memory map,中文意思是 内存映射。其用途是将文件映射到内存中,然后可以通过对映射区的内存进行读写操作,其效果等同于对文件进行读写操作。[8]
磁盘文件和内存都是字节序列,它们并没有什么区别。解析出要加载哪部分到内存,直接mmap就可以。而且可以访问多少文件加载多少。[9]
3 运行时内存布局 Memory Layout
回答的问题:
1 dump文件里变量,函数的指针地址是怎么确定的
2 堆内存块大小,栈内存块大小如何决定
编译的目标文件(object files)会分成不同的段。这样操作系统就方便的对不同程序可以按策略加载和移除程序到内存中。需要重申(reiterate)的是,一个C程序在内存中加载和运行时,包含多个段(segment)。这些段在程序编译成可执行文件时已经创建成型了。编译器分配了程序和数据到不同的段。可执行文件的头(header)包含了这些段的信息,如大小,偏移(offset)等。
分段的作用:[1:2]
- Multiprogramming[10]: CPU 在不同任务之间轮转, 实现同时运行多个程序
- Memory protection
- Dynamic relocation
程序编译后分成了主要段包括:
-
代码段 (Code Segment)
-
数据段 (Data Segment)
-
BSS段Block Started by Symbol
-
栈段 (Stack)
-
堆段 (Heap)
进程内存分布图以及栈存储的东西: 【程序在内存中的分布】
代码示例:程序在内存中的各个段 [11]
int init_global = 10; // data segment,静态存储区
int uninit_global; // BSS segment,静态存储区
int main(nt argc, char const *argv[] )
{
static int init_static_var = 0; // data segment,静态存储区
static int uninit_static_var; // BSS segment,静态存储区
int b; // stack
char *p1 = "123456"; // p1在栈上,“123456\0"在常量区(.rodata)
char *p2; // stack
p2 = (char *)malloc(20); // 堆段(heap) malloc分配
free(p2); // 堆段(heap) free释放
return 0;
}
内核栈是每个进程保留在内核内存中的内存区域,供内核内部函数调用时使用。用户栈驻留在不受保护(用户可操作)的用户内存中。
pmap
命令查看进程内存分布
//方法1
(gdb) starti
(gdb) info inferiors
Num Description Executable
* 1 process 765740 /root/func <<< 进程号
(gdb) !pmap 765740
765740: /root/func
0000555555554000 4K r---- func
0000555555555000 4K r-x-- func <<< add, main函数位置
0000555555556000 4K r---- func
0000555555557000 4K r---- func
0000555555558000 4K rw--- func
00007ffff7dc3000 136K r---- libc-2.31.so
00007ffff7de5000 1504K r-x-- libc-2.31.so
00007ffff7f5d000 312K r---- libc-2.31.so
00007ffff7fab000 16K r---- libc-2.31.so
00007ffff7faf000 8K rw--- libc-2.31.so
00007ffff7fb1000 24K rw--- [ anon ]
00007ffff7fc9000 16K r---- [ anon ]
00007ffff7fcd000 8K r-x-- [ anon ]
00007ffff7fcf000 4K r---- ld-2.31.so
00007ffff7fd0000 140K r-x-- ld-2.31.so
00007ffff7ff3000 32K r---- ld-2.31.so
00007ffff7ffc000 4K r---- ld-2.31.so
00007ffff7ffd000 4K rw--- ld-2.31.so
00007ffff7ffe000 4K rw--- [ anon ]
00007ffffffde000 132K rw--- [ stack ]
ffffffffff600000 4K --x-- [ anon ]
total 2368K
//方法2
# pmap -x 765740
765740: /root/func
Address Kbytes RSS Dirty Mode Mapping
0000555555554000 4 4 0 r---- func
0000555555555000 4 4 4 r-x-- func <<< add, main函数位置
0000555555556000 4 0 0 r---- func
0000555555557000 4 4 4 r---- func
0000555555558000 4 4 4 rw--- func
00007ffff7dc3000 136 136 0 r---- libc-2.31.so
00007ffff7de5000 1504 556 0 r-x-- libc-2.31.so
00007ffff7f5d000 312 64 0 r---- libc-2.31.so
00007ffff7fab000 16 16 16 r---- libc-2.31.so
00007ffff7faf000 8 8 8 rw--- libc-2.31.so
00007ffff7fb1000 24 16 16 rw--- [ anon ]
00007ffff7fc9000 16 0 0 r---- [ anon ]
00007ffff7fcd000 8 8 0 r-x-- [ anon ]
00007ffff7fcf000 4 4 0 r---- ld-2.31.so
00007ffff7fd0000 140 140 24 r-x-- ld-2.31.so
00007ffff7ff3000 32 32 0 r---- ld-2.31.so
00007ffff7ffc000 4 4 4 r---- ld-2.31.so
00007ffff7ffd000 4 4 4 rw--- ld-2.31.so
00007ffff7ffe000 4 4 4 rw--- [ anon ]
00007ffffffde000 132 12 12 rw--- [ stack ]
ffffffffff600000 4 0 0 --x-- [ anon ]
---------------- ------- ------- -------
total kB 2368 1020 100
//方法3: linux下一切皆文件
# ps -ef | grep func <<< 进程号
root 765740 765738 0 13:49 pts/13 00:00:00 /root/func
# cat /proc/765740/maps
555555554000-555555555000 r--p 00000000 08:05 1709668 /root/func
555555555000-555555556000 r-xp 00001000 08:05 1709668 /root/func <<< add, main函数位置
555555556000-555555557000 r--p 00002000 08:05 1709668 /root/func
555555557000-555555558000 r--p 00002000 08:05 1709668 /root/func
555555558000-555555559000 rw-p 00003000 08:05 1709668 /root/func
7ffff7dc3000-7ffff7de5000 r--p 00000000 08:05 2885782 /usr/lib/libc-2.31.so
7ffff7de5000-7ffff7f5d000 r-xp 00022000 08:05 2885782 /usr/lib/libc-2.31.so
7ffff7f5d000-7ffff7fab000 r--p 0019a000 08:05 2885782 /usr/lib/libc-2.31.so
7ffff7fab000-7ffff7faf000 r--p 001e7000 08:05 2885782 /usr/lib/libc-2.31.so
7ffff7faf000-7ffff7fb1000 rw-p 001eb000 08:05 2885782 /usr/lib/libc-2.31.so
7ffff7fb1000-7ffff7fb7000 rw-p 00000000 00:00 0
7ffff7fc9000-7ffff7fcd000 r--p 00000000 00:00 0 [vvar]
7ffff7fcd000-7ffff7fcf000 r-xp 00000000 00:00 0 [vdso]
7ffff7fcf000-7ffff7fd0000 r--p 00000000 08:05 2885775 /usr/lib/ld-2.31.so
7ffff7fd0000-7ffff7ff3000 r-xp 00001000 08:05 2885775 /usr/lib/ld-2.31.so
7ffff7ff3000-7ffff7ffb000 r--p 00024000 08:05 2885775 /usr/lib/ld-2.31.so
7ffff7ffc000-7ffff7ffd000 r--p 0002c000 08:05 2885775 /usr/lib/ld-2.31.so
7ffff7ffd000-7ffff7ffe000 rw-p 0002d000 08:05 2885775 /usr/lib/ld-2.31.so
7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
3.1 代码段 (Code Segment)
存放:
- 机器指令代码(machine instruction code)
运行相同二进制文件(binary)的不同进程可以共享代码段。代码段内存有读写权限。
通过gdb可以调出程序运行时的代码段:
计算机里的概念可以互相验证。运行时内存可以跟readelf看到的可执行文件里的汇编指令地址相互佐证。
(gdb) layout asm
0x555555555030 <__cxa_finalize@plt> endbr64
0x555555555034 <__cxa_finalize@plt+4> bnd jmpq *0x2fbd(%rip) # 0x555555557ff8
0x55555555503b <__cxa_finalize@plt+11> nopl 0x0(%rax,%rax,1)
// 1. 程序加载入口
0x555555555040 <_start> endbr64
0x555555555044 <_start+4> xor %ebp,%ebp
0x555555555046 <_start+6> mov %rdx,%r9
0x555555555049 <_start+9> pop %rsi
0x55555555504a <_start+10> mov %rsp,%rdx
0x55555555504d <_start+13> and $0xfffffffffffffff0,%rsp
0x555555555051 <_start+17> push %rax
0x555555555052 <_start+18> push %rsp
0x555555555053 <_start+19> lea 0x196(%rip),%r8 # 0x5555555551f0 <__libc_csu_fini>
0x55555555505a <_start+26> lea 0x11f(%rip),%rcx # 0x555555555180 <__libc_csu_init>
// 2. 调用main函数
0x555555555061 <_start+33> lea 0xd9(%rip),%rdi # 0x555555555141 <main>
0x555555555068 <_start+40> callq *0x2f72(%rip) # 0x555555557fe0
0x55555555506e <_start+46> hlt
0x55555555506f nop
0x555555555070 <deregister_tm_clones> lea 0x2f99(%rip),%rdi # 0x555555558010 <completed.8061>
0x555555555077 <deregister_tm_clones+7> lea 0x2f92(%rip),%rax # 0x555555558010 <completed.8061>
0x55555555507e <deregister_tm_clones+14> cmp %rdi,%rax
0x555555555081 <deregister_tm_clones+17> je
0x555555555098 <deregister_tm_clones+40>
0x555555555083 <deregister_tm_clones+19> mov 0x2f4e(%rip),%rax # 0x555555557fd8
0x55555555508a <deregister_tm_clones+26> test %rax,%rax
0x55555555508d <deregister_tm_clones+29> je
0x555555555098 <deregister_tm_clones+40>
0x55555555508f <deregister_tm_clones+31> jmpq *%rax
0x555555555091 <deregister_tm_clones+33> nopl 0x0(%rax) 0x555555555098 <deregister_tm_clones+40> retq
0x555555555099 <deregister_tm_clones+41> nopl 0x0(%rax)
0x5555555550a0 <register_tm_clones> lea 0x2f69(%rip),%rdi # 0x555555558010 <completed.8061>
0x5555555550a7 <register_tm_clones+7> lea 0x2f62(%rip),%rsi # 0x555555558010 <completed.8061>
0x5555555550ae <register_tm_clones+14> sub %rdi,%rsi
0x5555555550b1 <register_tm_clones+17> mov %rsi,%rax
0x5555555550b4 <register_tm_clones+20> shr $0x3f,%rsi
0x5555555550b8 <register_tm_clones+24> sar $0x3,%rax
0x5555555550bc <register_tm_clones+28> add %rax,%rsi
0x5555555550bf <register_tm_clones+31> sar %rsi
0x5555555550c2 <register_tm_clones+34> je 0x5555555550d8 <register_tm_clones+56>
0x5555555550c4 <register_tm_clones+36> mov 0x2f25(%rip),%rax # 0x555555557ff0
0x5555555550cb <register_tm_clones+43> test %rax,%rax
0x5555555550ce <register_tm_clones+46> je 0x5555555550d8 <register_tm_clones+56>
0x5555555550d0 <register_tm_clones+48> jmpq *%rax
0x5555555550d2 <register_tm_clones+50> nopw 0x0(%rax,%rax,1)
0x5555555550d8 <register_tm_clones+56> retq
0x5555555550d9 <register_tm_clones+57> nopl 0x0(%rax)
0x5555555550e0 <__do_global_dtors_aux> endbr64
0x5555555550e4 <__do_global_dtors_aux+4> cmpb $0x0,0x2f25(%rip) # 0x555555558010 <completed.8061>
0x5555555550eb <__do_global_dtors_aux+11> jne
0x555555555118 <__do_global_dtors_aux+56>
0x5555555550ed <__do_global_dtors_aux+13> push %rbp
0x5555555550ee <__do_global_dtors_aux+14> cmpq $0x0,0x2f02(%rip) # 0x555555557ff8
0x5555555550f6 <__do_global_dtors_aux+22> mov %rsp,%rbp
0x5555555550f9 <__do_global_dtors_aux+25> je 0x555555555107 <__do_global_dtors_aux+39>
0x5555555550fb <__do_global_dtors_aux+27> mov 0x2f06(%rip),%rdi # 0x555555558008
0x555555555102 <__do_global_dtors_aux+34> callq 0x555555555030 <__cxa_finalize@plt>
0x555555555107 <__do_global_dtors_aux+39> callq 0x555555555070 <deregister_tm_clones>
0x55555555510c <__do_global_dtors_aux+44> movb $0x1,0x2efd(%rip) # 0x555555558010 <completed.8061>
0x555555555113 <__do_global_dtors_aux+51> pop %rbp
0x555555555114 <__do_global_dtors_aux+52> retq
0x555555555115 <__do_global_dtors_aux+53> nopl (%rax)
0x555555555118 <__do_global_dtors_aux+56> retq
0x555555555119 <__do_global_dtors_aux+57> nopl 0x0(%rax)
0x555555555120 <frame_dummy> endbr64
0x555555555124 <frame_dummy+4> jmpq
0x5555555550a0 <register_tm_clones>
// 3. add函数入口地址
0x555555555129 <add> endbr64
0x55555555512d <add+4> push %rbp
0x55555555512e <add+5> mov %rsp,%rbp
0x555555555131 <add+8> mov %edi,-0x4(%rbp)
0x555555555134 <add+11> mov %esi,-0x8(%rbp)
0x555555555137 <add+14> mov -0x4(%rbp),%edx
0x55555555513a <add+17> mov -0x8(%rbp),%eax
0x55555555513d <add+20> add %edx,%eax
0x55555555513f <add+22> pop %rbp
0x555555555140 <add+23> retq
// 4. main函数入口地址
0x555555555141 <main> endbr64
0x555555555145 <main+4> push %rbp
0x555555555146 <main+5> mov %rsp,%rbp
0x555555555149 <main+8> sub $0x10,%rsp
0x55555555514d <main+12> movl $0xa,-0xc(%rbp)
0x555555555154 <main+19> movl $0x14,-0x8(%rbp)
0x55555555515b <main+26> mov $0x14,%esi
0x555555555160 <main+31> mov $0xa,%edi
// 4.1 调用add函数地址
0x555555555165 <main+36> callq 0x555555555129 <add>
0x55555555516a <main+41> mov %eax,-0x4(%rbp)
0x55555555516d <main+44> mov -0x4(%rbp),%eax
0x555555555170 <main+47> leaveq
0x555555555171 <main+48> retq
0x555555555172 nopw %cs:0x0(%rax,%rax,1)
0x55555555517c nopl 0x0(%rax)
0x555555555180 <__libc_csu_init> endbr64
0x555555555184 <__libc_csu_init+4> push %r15
0x555555555186 <__libc_csu_init+6> lea 0x2c63(%rip),%r15 # 0x555555557df0
0x55555555518d <__libc_csu_init+13> push %r14
0x55555555518f <__libc_csu_init+15> mov %rdx,%r14
0x555555555192 <__libc_csu_init+18> push %r13
0x555555555194 <__libc_csu_init+20> mov %rsi,%r13
0x555555555197 <__libc_csu_init+23> push %r12
0x555555555199 <__libc_csu_init+25> mov %edi,%r12d
0x55555555519c <__libc_csu_init+28> push %rbp
0x55555555519d <__libc_csu_init+29> lea 0x2c54(%rip),%rbp # 0x555555557df8
0x5555555551a4 <__libc_csu_init+36> push %rbx
0x5555555551a5 <__libc_csu_init+37> sub %r15,%rbp
0x5555555551a8 <__libc_csu_init+40> sub $0x8,%rsp
0x5555555551ac <__libc_csu_init+44> callq 0x555555555000 <_init>
0x5555555551b1 <__libc_csu_init+49> sar $0x3,%rbp
0x5555555551b5 <__libc_csu_init+53> je
0x5555555551d6 <__libc_csu_init+86>
0x5555555551b7 <__libc_csu_init+55> xor %ebx,%ebx
0x5555555551b9 <__libc_csu_init+57> nopl 0x0(%rax)
0x5555555551c0 <__libc_csu_init+64> mov %r14,%rdx
0x5555555551c3 <__libc_csu_init+67> mov %r13,%rsi
0x5555555551c6 <__libc_csu_init+70> mov %r12d,%edi
0x5555555551c9 <__libc_csu_init+73> callq *(%r15,%rbx,8)
0x5555555551cd <__libc_csu_init+77> add $0x1,%rbx
0x5555555551d1 <__libc_csu_init+81> cmp %rbx,%rbp
0x5555555551d4 <__libc_csu_init+84> jne 0x5555555551c0 <__libc_csu_init+64>
0x5555555551d6 <__libc_csu_init+86> add $0x8,%rsp
0x5555555551da <__libc_csu_init+90> pop %rbx
0x5555555551db <__libc_csu_init+91> pop %rbp
0x5555555551dc <__libc_csu_init+92> pop %r12
0x5555555551de <__libc_csu_init+94> pop %r13
0x5555555551e0 <__libc_csu_init+96> pop %r14
0x5555555551e2 <__libc_csu_init+98> pop %r15
0x5555555551e4 <__libc_csu_init+100> retq
0x5555555551e5 data16 nopw %cs:0x0(%rax,%rax,1)
0x5555555551f0 <__libc_csu_fini> endbr64
0x5555555551f4 <__libc_csu_fini+4> retq
0x5555555551f5 add %al,(%rax)
0x5555555551f7 add %dh,%bl
0x5555555551f9 <_fini+1> nop %edx
0x5555555551fc <_fini+4> sub $0x8,%rsp
0x555555555200 <_fini+8> add $0x8,%rsp
0x555555555204 <_fini+12> retq
0x555555555205 add %al,(%rax)
0x555555555207 add %al,(%rax)
0x555555555209 add %al,(%rax)
3.2 数据段 (Data Segment)
存放:
- 初始化为非零值的全局变量(initialized global variable)
- 初始化为非零值的静态变量(initialized static variable)
运行相同程序的每个进程都有一份私有数据段拷贝。静态数据区,static变量和全局变量的生命周期是一样的。
3.3 未初始化/BSS段
BSS: Block Started by Symbol
存放:
- 未初始化的全局变量(uninitialized global variable),默认为零值
- 未初始化的静态变量(uninitialized static variable),默认为零值
3.4 栈段 (Stack)
存放:
- 局部变量(local variable)
- 函数参数(function parameter)
- 函数调用返回地址(return address from a function call)
backtrace
(gdb) bt
#0 add () at func.s:14
#1 0x000055555555516a in main () at func.s:41
栈帧
程序加载完成后,运行从main函数开始。然后CPU就执行函数里的指令和数据不断去操作内存。
运行时代码段内存如下:
内存地址 | 源代码 | 汇编代码 |
---|---|---|
0x555555555129 0x55555555512d <add+4> 0x55555555512e <add+5> 0x555555555131 <add+8> 0x555555555134 <add+11> 0x555555555137 <add+14> 0x55555555513a <add+17> 0x55555555513d <add+20> 0x55555555513f <add+22> 0x555555555140 <add+23> |
int add(int x, int y) { return x+y; } |
endbr64 push %rbp mov %rsp,%rbp mov %edi,-0x4(%rbp) mov %esi,-0x8(%rbp) mov -0x4(%rbp),%edx mov -0x8(%rbp),%eax add %edx,%eax pop %rbp retq |
0x555555555141 0x555555555145 <main+4> 0x555555555146 <main+5> 0x555555555149 <main+8> 0x55555555514d <main+12> 0x555555555154 <main+19> 0x55555555515b <main+26> 0x555555555160 <main+31> 0x555555555165 <main+36> 0x55555555516a <main+41> 0x55555555516d <main+44> 0x555555555170 <main+47> 0x555555555171 <main+48> |
int main() { int a = 10; int b = 20; int c; c = add(10,20); return 0; } |
endbr64 push %rbp mov %rsp,%rbp sub $0x10,%rsp movl $0xa,-0xc(%rbp) movl $0x14,-0x8(%rbp) mov $0x14,%esi mov $0xa,%edi callq 0x555555555129 mov %eax,-0x4(%rbp) mov -0x4(%rbp),%eax leaveq retq |
寄存器:
1 %esp stack pointer(栈顶指针)指向栈最上面一个栈帧的栈顶
2 %ebp(栈底指针)指向栈最上面一个栈帧的栈底
%esp - %ebp = 栈的大小
函数内的变量都可以通过%ebp加偏移量找到
-
每次调用到一个新函数时,会在栈上新分配一个栈祯(push 入栈)。
调用add函数时,将参数从右往左入栈(Push)
0x200000000 main()
0x200000004 {
0x200000084 int a = 10;
0x200000089 int b = 20;
0x200000100 int c;
0x200000104 c = add( 10, 20); < ------ CALL INSTR
0x200000108 c++; < ------ EIP
0x200000110 }
-
调用函数。程序要记录函数返回后的地址,处理器将EIP入栈。EIP指向add函数返回后的下一条语句。
-
更新%ebp。此时已经进入调用的
add()
函数 -
函数内部定义的临时变量依次分配栈内存,局部变量多,则栈帧大。
-
add()
函数 return后出栈(Pop)。运行到return处,函数返回时,从栈上将此栈帧弹出(pop 出栈)。- 释放栈帧
-
走到函数return的地方就跳回刚才记录的函数位置,然后接着往下执行。
异常情况: [14]
-
栈溢出(stack overflow )
当递归函数时,栈帧不停增长,栈内存放不下就栈溢出。
访问写函数内存以外的地方内存,比如数组越界。甚至覆盖其他栈帧的返回地址
如果函数位置丢失了,程序可能去其他地方了,可能是另一个函数或其他函数的变量位置,不可预测。
#1 func()
#0 main() -> ??? 找不到地址了,刚好是其他函数地址就显示成那个函数名
3.5 堆段 (Heap)
存放:
- malloc(), calloc(), and realloc()分配的内存 [15]
异常情况:
- 泄漏风险: memory leaks.
- 内存碎片 [16]
栈与堆比较[17]
-
栈是先进后出的。栈空间的增长方向是从高地址向低地址增长。
-
堆是先进先出的。堆空间的增长方向是从低地址向高地址增长。
-
堆空间远大于栈。
-
栈向堆移动,堆向栈移动,把中间的内存挤空了,内存就用光了
问题:栈总内存能有多大?