浅谈内存映射

程序启动过程简介

Linux为例,Linux在启动以后为每个终端用户建立一个进程执行shell解释程序,该程序接收病执行命令的过程如下:

  • 读取命令行,分析命令,以命令名作为文件名,相关参数传入execve内部处理,执行execve后,引发int 0x80中断,进入内核态;
  • 终端进程调用fork建立一个子进程,并系统调用wait4等待,子进程根据文件名找到相关文件,将其调入内存,执行这个程序;
  • 若命令末尾有&则终端不需要系统调用wait4等待。

Linux支持不同的可执行文件格式,通过struct linux_binfmt结构体管理不同的可执行文件,这个结构中有对应的可执行文件的处理函数。
总体过程如下:

  • 进入内核态后,do_execve函数读入可执行文件,检查权限,读取可执行文件的相关信息;
  • 根据文件类型,在struct linux_binfmt中找到相应的load_binary()函数(对于ELF格式的文件,处理函数是load_elf_binary),加载可执行文件

ELF文件格式为例,其加载可执行文件的过程如下:

  • 读入ELf文件的头部,根据头部信息读入各种数据,扫描程序段描述符(Program Header Table),找到类型为PT_LOAD的段(即.text, .data, .bss等节区)
  • 将段映射到内存的固定地址上,如果没有动态连接器的描述段,则返回入口地址为应用程序入口;
  • 若应用程序使用了动态链接库,除加载可执行文件外,控制权交给动态链接器(ld-linux.so)。内核搜寻段表(Program Header Table),找到标记为 PT_INTERP 段中所对应的动态连接器的名称,并使用 load_elf_interp 加载其映像,并把返回的入口地址设置成 load_elf_interp 的返回值,即动态链接器的入口;

ELF文件

ELF 文件的大体结构:

----------------------
ELF Header            |    #程序头,有该文件的Magic number(参考man magic),类型等
----------------------
Program Header Table  |    #对可执行文件和共享库有效,它描述下面各个节(section)组成的段
----------------------
Section1              |
----------------------
Section2              |
----------------------
Section3              |
----------------------
.....                 |
----------------------
Program Section Table |  #仅对可重定位目标文件和静态库有效,用于描述各个Section的重定位信息等。
----------------------

ELF 文件的主要节区(section)有 .data,.text,.bss,.interp 等,而主要段(segment)有 LOAD,INTERP 等。

section 解释 实例
.data 初始化的数据 比如 int a=10
.bss 未初始化的数据 比如 char sum[100];这个在程序执行之前,内核将初始化为0
.text 程序代码正文 即可执行指令集
.interp 描述程序需要的解析器(动态连接和装载程序) 存在解释器的全路径,如/lib/ld-linux.so

示例

ELF Header:

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          712 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           64 (bytes)
  Number of section headers:         13
  Section header string table index: 12

Section Headers:
ELF 文件的主要节区(section)有 .data,.text,.bss,.interp 等,而主要段(segment)有 LOAD,INTERP 等。

  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00000040
       0000000000000017  0000000000000000  AX       0     0     1
  [ 2] .rela.text        RELA             0000000000000000  00000218
       0000000000000030  0000000000000018   I      10     1     8
  [ 3] .data             PROGBITS         0000000000000000  00000057
       0000000000000000  0000000000000000  WA       0     0     1
  [ 4] .bss              NOBITS           0000000000000000  00000057
       0000000000000000  0000000000000000  WA       0     0     1
  [ 5] .rodata           PROGBITS         0000000000000000  00000057
       000000000000000d  0000000000000000   A       0     0     1
  [ 6] .comment          PROGBITS         0000000000000000  00000064
       000000000000002a  0000000000000001  MS       0     0     1
  [ 7] .note.GNU-stack   PROGBITS         0000000000000000  0000008e
       0000000000000000  0000000000000000           0     0     1
  [ 8] .eh_frame         PROGBITS         0000000000000000  00000090
       0000000000000038  0000000000000000   A       0     0     8
  [ 9] .rela.eh_frame    RELA             0000000000000000  00000248
       0000000000000018  0000000000000018   I      10     8     8
  [10] .symtab           SYMTAB           0000000000000000  000000c8
       0000000000000120  0000000000000018          11     9     8
  [11] .strtab           STRTAB           0000000000000000  000001e8
       000000000000002e  0000000000000000           0     0     1
  [12] .shstrtab         STRTAB           0000000000000000  00000260
       0000000000000061  0000000000000000           0     0     1

Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)
#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("hello world!\n");
    return 0;
}

对应的汇编代码如下:

        .file   "helloworld.c"
        .text
        .section        .rodata
.LC0:
        .string "hello world!"
        .text
        .globl  main
        .type   main, @function
main:
.LFB5:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        leaq    .LC0(%rip), %rdi
        call    puts@PLT
        movl    $0, %eax
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE5:
        .size   main, .-main
        .ident  "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
        .section        .note.GNU-stack,"",@progbits
寄存器 名称 注释
EIP/RAX 程序指令指针 通常指向下一条指令的位置
ESP/RSP 程序堆栈指针 通常指向当前堆栈的当前位置
EBP/RBP 程序基指针 通常指向函数使用的堆栈顶端
 void main()
 {
     __asm__ __volatile__("jmp forward;"
        "backward:"
        "popl   %esi;"
        "movl   $4, %eax;"
        "movl   $2, %ebx;"
        "movl   %esi, %ecx;"
        "movl   $12, %edx;"
        "int    $0x80;"
        "movl   $1, %eax;"
        "movl   $0, %ebx;"
        "int    $0x80;"
        "forward:"
        "call   backward;"
        ".string \"Hello World\\n\";");
}
  • %rax 作为函数返回值使用。
  • %rsp 栈指针寄存器,指向栈顶
  • %rdi,%rsi,%rdx,%rcx,%r8,%r9 用作函数参数,依次对应第1参数,第2参数。。。
  • %rbx,%rbp,%r12,%r13,%14,%15 用作数据存储,遵循被调用者使用规则,简单说就是随便用,调用子函数之前要备份它,以防他被修改
  • %r10,%r11 用作数据存储,遵循调用者使用规则,简单说就是使用之前要先保存原值

参考

Was this helpful?

0 / 0

发表回复 0