程序编译过程与运行时内存

原文地址:https://www.cnblogs.com/liqinglucky/p/compiler.html

1 内存

1.1 存储与类型

处理器获取数据的顺序:

  1. 寄存器

  2. 其次是高速缓存(cache),高速缓存(cache)分为L1 cache,L2 cache ,L3 cache。如果直到L3 cache才找到,就将数据从L3 cache拷贝至L2 cache,将L2 cache拷贝至L1 cache最后拷贝至寄存器。这样CPU就拿到了数据。

  3. 其次是主内存

  4. 最后是二级内存,如硬盘。

存储的类型:[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)

编译过程链[1:1][5]

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

程序编译后分成了主要段包括:

  1. 代码段 (Code Segment)

  2. 数据段 (Data Segment)

  3. BSS段Block Started by Symbol

  4. 栈段 (Stack)

  5. 堆段 (Heap)

进程内存分布图[1:3][5:1]

进程内存分布图以及栈存储的东西: 【程序在内存中的分布】

堆栈

代码示例:程序在内存中的各个段 [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加偏移量找到

汇编代码[12]解释函数调用时栈的变化 [13]

  1. 每次调用到一个新函数时,会在栈上新分配一个栈祯(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 }
  1. 调用函数。程序要记录函数返回后的地址,处理器将EIP入栈。EIP指向add函数返回后的下一条语句。

  2. 更新%ebp。此时已经进入调用的add()函数

  3. 函数内部定义的临时变量依次分配栈内存,局部变量多,则栈帧大。

  4. add()函数 return后出栈(Pop)。运行到return处,函数返回时,从栈上将此栈帧弹出(pop 出栈)。

    • 释放栈帧
  5. 走到函数return的地方就跳回刚才记录的函数位置,然后接着往下执行。

异常情况: [14]

  1. 栈溢出(stack overflow )

    当递归函数时,栈帧不停增长,栈内存放不下就栈溢出。

    访问写函数内存以外的地方内存,比如数组越界。甚至覆盖其他栈帧的返回地址

如果函数位置丢失了,程序可能去其他地方了,可能是另一个函数或其他函数的变量位置,不可预测。

#1 func()
#0 main()   -> ??? 找不到地址了,刚好是其他函数地址就显示成那个函数名

3.5 堆段 (Heap)

存放:

  • malloc(), calloc(), and realloc()分配的内存 [15]

异常情况:

  1. 泄漏风险: memory leaks.
  2. 内存碎片 [16]

栈与堆比较[17]

  • 栈是先进后出的。栈空间的增长方向是从高地址向低地址增长。

  • 堆是先进先出的。堆空间的增长方向是从低地址向高地址增长。

  • 堆空间远大于栈。

  • 栈向堆移动,堆向栈移动,把中间的内存挤空了,内存就用光了

问题:栈总内存能有多大?

参考


  1. Toppo N , Dewan H . Pointers in C[J]. ↩︎ ↩︎ ↩︎ ↩︎

  2. 寄存器与内存间的桥梁(cache) ↩︎

  3. 计算机是如何工作的?探索主内存,以DDR5为例 ↩︎

  4. CSAPP-深入理解计算机系统 7-7. 可执行目标文件 ↩︎

  5. RandalE.Bryant, DavidO'Hallaron, 龚奕利,等. 深入理解计算机系统[J]. 中国电力出版社, 2004. ↩︎ ↩︎

  6. 程序如何运行?编译、链接、装入? ↩︎ ↩︎

  7. 加载动态库,是将动态库内所有的模块都加载到内存中吗? - 知乎 ↩︎

  8. Linux黑科技|mmap实现详解 ↩︎

  9. 进程的地址空间 (pmap; vdso; mmap; 游戏修改器/外挂)南京大学2022操作系统-P12 ↩︎

  10. What is multiprogramming? (techtarget.com) ↩︎

  11. 编程基础-程序在内存中的各个段 ↩︎

  12. 汇编代码:汇编 - 理解函数调用栈 - 简书 (jianshu.com) ↩︎

  13. C程序在内存中的栈 ↩︎

  14. C 程序内存管理中,一不小心可能会跑飞的 <栈> ↩︎

  15. 你用malloc申请的内存空间是怎么分配的? ↩︎

  16. 动态分配—堆 ↩︎

  17. 什么是堆栈、栈、堆 ↩︎

posted @ 2023-03-28 09:39  liqinglucky  阅读(316)  评论(0编辑  收藏  举报