1、地址空间

在linux系统中,每个进程拥有自己独立的虚拟地址空间,这个虚拟地址空间的大小是由计算机硬件决定的,具体地说,是由CPU的位数决定的。比如,32位硬件平台决定的虚拟地址空间大小:0——232-1。即0x00000000——0xFFFFFFFF。

  在linux系统中,整个4GB内存空间被分成两部分。其中操作系统本身用去一部分,从地址0xC0000000到0xFFFFFFFF。剩下的低地址的0x00000000到0xBFFFFFFF的3GB虚拟空间由进程使用。

  装载有两种方式——覆盖装入与页映射。现在操作系统主要是页映射方式。装载过程在创建进程时主要做以下三件事:

1、创建一个独立的虚拟地址空间;

2、读取可执行文件头,并且建立虚拟地址空间与可执行文件的映射;

3、将CPU指令寄存器设置成可执行文件的入口地址,启动执行。

创建虚拟地址空间并不是创建空间,而是创建映射函数所需要的数据结构,实际上就是分配一个页目录,甚至不用设置页映射关系,这些映射关系可以等到程序发生页错误时在进行设置。

读取可执行文件头,并且建立虚拟空间与可执行文件的映射关系:当程序发生页错误的时候,操作系统将从物理内存中分配一个物理页,然后将该”缺页”从磁盘中读取到内存中,再设置缺页的虚拟页和物理页的映射关系。当操作系统捕获到缺页错误时,它应知道程序当前所需要的页在可执行文件中的哪一个位置。这就是虚拟空间与可执行文件之间的映射关系。Linux中将进程虚拟空间中的一个段叫做虚拟内存区域(VMA),在Windows中将这个叫做”虚拟段”。
将CPU指令寄存器设置成可执行文件入口,启动运行:操作系统通过设置CPU的指令寄存器将控制权交给进程,由此进程开始执行。

2、页错误

当CPU开始打算执行一个地址指令时,发现页面是空页面,于是它认为这是一个页错误。CPU将控制权交给操作系统,操作系统将查询第二步建立的数据结构,然后找到VMA,计算出相应的页面在可执行文件中的偏移,然后在物理内存中分配一个物理页面,将进程中该虚拟页与分配的物理页之间建立映射关系,然后把控制权交回给进程,进程从刚才错误页位置重新开始执行。

3、 进程虚存空间分布

3.1、ELF文件链接视图和执行视图

如果可执行文件只有一个代码段,所有它被操作系统转载至进程地址空间之后,相对应的只有一个VMA。
当段的数量增多时,就会产生空间浪费问题。为了避免这种问题,可以把相同的段合并。

看一个例子:

  1. 1 #include<stdlib.h>
  2. 2
  3. 3 int main()
  4. 4 {
  5. 5 while(1)
  6. 6 {
  7. 7 sleep(1000);
  8. 8 }
  9. 9 return 0;
  10. 10 }

gcc -static SectionMapping.c -o SectionMapping.elf

下面看一下,上述elf文件的section分布。

  1. [root@tlinux /]# readelf -S SectionMapping.elf
  2. There are 34 section headers, starting at offset 0xd0aa0:
  3.  
  4. Section Headers:
  5. [Nr] Name Type Address Offset
  6. Size EntSize Flags Link Info Align
  7. [ 0] NULL 0000000000000000 00000000
  8. 0000000000000000 0000000000000000 0 0 0
  9. [ 1] .note.ABI-tag NOTE 0000000000400190 00000190
  10. 0000000000000020 0000000000000000 A 0 0 4
  11. [ 2] .note.gnu.build-i NOTE 00000000004001b0 000001b0
  12. 0000000000000024 0000000000000000 A 0 0 4
  13. [ 3] .rela.plt RELA 00000000004001d8 000001d8
  14. 0000000000000108 0000000000000018 AI 0 25 8
  15. [ 4] .init PROGBITS 00000000004002e0 000002e0
  16. 000000000000001a 0000000000000000 AX 0 0 4
  17. [ 5] .plt PROGBITS 0000000000400300 00000300
  18. 00000000000000b0 0000000000000000 AX 0 0 16
  19. [ 6] .text PROGBITS 00000000004003b0 000003b0
  20. 0000000000091916 0000000000000000 AX 0 0 16
  21. [ 7] __libc_thread_fre PROGBITS 0000000000491cd0 00091cd0
  22. 00000000000000b2 0000000000000000 AX 0 0 16
  23. [ 8] __libc_freeres_fn PROGBITS 0000000000491d90 00091d90
  24. 0000000000001aef 0000000000000000 AX 0 0 16
  25. [ 9] .fini PROGBITS 0000000000493880 00093880
  26. 0000000000000009 0000000000000000 AX 0 0 4
  27. [10] .rodata PROGBITS 00000000004938a0 000938a0
  28. 00000000000196f8 0000000000000000 A 0 0 32
  29. [11] .stapsdt.base PROGBITS 00000000004acf98 000acf98
  30. 0000000000000001 0000000000000000 A 0 0 1
  31. [12] __libc_thread_sub PROGBITS 00000000004acfa0 000acfa0
  32. 0000000000000008 0000000000000000 A 0 0 8
  33. [13] __libc_subfreeres PROGBITS 00000000004acfa8 000acfa8
  34. 0000000000000050 0000000000000000 A 0 0 8
  35. [14] __libc_IO_vtables PROGBITS 00000000004ad000 000ad000
  36. 00000000000006a8 0000000000000000 A 0 0 32
  37. [15] __libc_atexit PROGBITS 00000000004ad6a8 000ad6a8
  38. 0000000000000008 0000000000000000 A 0 0 8
  39. [16] .eh_frame PROGBITS 00000000004ad6b0 000ad6b0
  40. 000000000000e234 0000000000000000 A 0 0 8
  41. [17] .gcc_except_table PROGBITS 00000000004bb8e4 000bb8e4
  42. 0000000000000105 0000000000000000 A 0 0 1
  43. [18] .tdata PROGBITS 00000000006bbeb0 000bbeb0
  44. 0000000000000020 0000000000000000 WAT 0 0 16
  45. [19] .tbss NOBITS 00000000006bbed0 000bbed0
  46. 0000000000000038 0000000000000000 WAT 0 0 16
  47. [20] .init_array INIT_ARRAY 00000000006bbed0 000bbed0
  48. 0000000000000010 0000000000000008 WA 0 0 8
  49. [21] .fini_array FINI_ARRAY 00000000006bbee0 000bbee0
  50. 0000000000000010 0000000000000008 WA 0 0 8
  51. [22] .jcr PROGBITS 00000000006bbef0 000bbef0
  52. 0000000000000008 0000000000000000 WA 0 0 8
  53. [23] .data.rel.ro PROGBITS 00000000006bbf00 000bbf00
  54. 00000000000000e4 0000000000000000 WA 0 0 32
  55. [24] .got PROGBITS 00000000006bbfe8 000bbfe8
  56. 0000000000000008 0000000000000008 WA 0 0 8
  57. [25] .got.plt PROGBITS 00000000006bc000 000bc000
  58. 0000000000000070 0000000000000008 WA 0 0 8
  59. [26] .data PROGBITS 00000000006bc080 000bc080
  60. 0000000000001690 0000000000000000 WA 0 0 32
  61. [27] .bss NOBITS 00000000006bd720 000bd710
  62. 0000000000002158 0000000000000000 WA 0 0 32
  63. [28] __libc_freeres_pt NOBITS 00000000006bf878 000bd710
  64. 0000000000000030 0000000000000000 WA 0 0 8
  65. [29] .comment PROGBITS 0000000000000000 000bd710
  66. 000000000000002d 0000000000000001 MS 0 0 1
  67. [30] .note.stapsdt NOTE 0000000000000000 000bd740
  68. 0000000000000f88 0000000000000000 0 0 4
  69. [31] .symtab SYMTAB 0000000000000000 000be6c8
  70. 000000000000b9e8 0000000000000018 32 815 8
  71. [32] .strtab STRTAB 0000000000000000 000ca0b0
  72. 0000000000006870 0000000000000000 0 0 1
  73. [33] .shstrtab STRTAB 0000000000000000 000d0920
  74. 000000000000017b 0000000000000000 0 0 1
  75. Key to Flags:
  76. W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  77. L (link order), O (extra OS processing required), G (group), T (TLS),
  78. C (compressed), x (unknown), o (OS specific), E (exclude),
  79. l (large), p (processor specific)

操作系统至关心段的权限(可读,可写,可执行)。
ELF文件中,段的权限基本是三种:

  • 以代码段为代表的权限可读可执行的段
  • 以数据段和BSS段为代码的权限为可读可写的段
  • 以只读数据为代表的权限为只读的段

一个简单的方案:对于相同权限的段,把它们合并到一起当作一个段进行映射。
一个”Segment”包含一个或多个属性类似的”Section”。
从链接的角度看,ELF文件是按”Section”存储的,从装载的角度看ELF文件又可以按照”Segment”划分。
“Section”和”Segment”是从不同角度来划分同一个ELF文件的。这个在ELF种被称为视图,从”Section”的角度来看ELF文件就是链接视图,从”Segment”的角度来看就是执行视图

ELF可执行文件中有一个专门的数据结构叫做程序头表用来保存”Segment”信息。

数据段和BSS段唯一区别就是:数据段从文件中初始化内容,而BSS段的内容全都初始化为0。

程序的程序头可由readelf -l SectionMapping.elf 获取

  1. Elf file type is EXEC (Executable file)
  2. Entry point 0x400ecd
  3. There are 6 program headers, starting at offset 64
  4.  
  5. Program Headers:
  6. Type Offset VirtAddr PhysAddr
  7. FileSiz MemSiz Flags Align
  8. LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
  9. 0x00000000000bb9e9 0x00000000000bb9e9 R E 200000
  10. LOAD 0x00000000000bbeb0 0x00000000006bbeb0 0x00000000006bbeb0
  11. 0x0000000000001860 0x00000000000039f8 RW 200000
  12. NOTE 0x0000000000000190 0x0000000000400190 0x0000000000400190
  13. 0x0000000000000044 0x0000000000000044 R 4
  14. TLS 0x00000000000bbeb0 0x00000000006bbeb0 0x00000000006bbeb0
  15. 0x0000000000000020 0x0000000000000058 R 10
  16. GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
  17. 0x0000000000000000 0x0000000000000000 RW 10
  18. GNU_RELRO 0x00000000000bbeb0 0x00000000006bbeb0 0x00000000006bbeb0
  19. 0x0000000000000150 0x0000000000000150 R 1
  20.  
  21. Section to Segment mapping:
  22. Segment Sections...
  23. 00 .note.ABI-tag .note.gnu.build-id .rela.plt .init .plt .text __libc_thread_freeres_fn __libc_freeres_fn .fini .rodata .stapsdt.base __libc_thread_subfreeres __libc_subfreeres __libc_IO_vtables __libc_atexit .eh_frame .gcc_except_table
  24. 01 .tdata .init_array .fini_array .jcr .data.rel.ro .got .got.plt .data .bss __libc_freeres_ptrs
  25. 02 .note.ABI-tag .note.gnu.build-id
  26. 03 .tdata .tbss
  27. 04
  28. 05 .tdata .init_array .fini_array .jcr .data.rel.ro .got

6.4.2 堆和栈

操作系统通过使用VMA来对进程的地址空间进行管理。进程在执行的时候它还需要用到等空间。一个进程中的栈和堆分别对应一个VMA。
进程虚拟地址空间的概念:操作系统通过给进程空间划分一个个VMA来管理进程的虚拟空间;基本原则就是将相同权限属性的、有相同映像文件的映射成一个VMA;一个进程基本上可以分为几种VMA区域:

    • 代码VMA,权限只读、可执行;有映像文件。
    • 数据VMA,权限可读写、可执行;有映像文件。
    • 堆VMA,权限可读写、可执行;无映像文件,匿名,可向上扩展。
    • 栈VMA,权限可读写、不可执行;无映像文件,匿名,可向下扩展。

6.4.3 堆的最大申请数量

理论上Linum下虚拟地址空间分给进程本身的是3GB,win是2GB,但实际上,Linux分配的程序的最大内存是2.9GB,win分配的为1.5GB。
有些操作系统使用了一种叫做随机地址空间分布技术,使得进程的堆空间变小。

下面的代码可以简单查看操作系统可申请堆大小:

  1. 1 #include<stdio.h>
  2. 2 #include<stdlib.h>
  3. 3
  4. 4 unsigned maxinum=0;
  5. 5 int main()
  6. 6 {
  7. 7
  8. 8 unsigned blocked[]={1024*1024,1024,1};
  9. 9 int i,count;
  10. 10 for(i=0;i<3;i++){
  11. 11 for(count=1;;count++){
  12. 12 void* block=malloc(maxinum+blocked[i]*count);
  13. 13 if(block){
  14. 14 maxinum=maxinum+blocked[i]*count;
  15. 15 }else{
  16. 16
  17. 17 break;
  18. 18 }
  19. 19 }
  20. 20 }
  21. 21 printf("maxinum malloc size= %u bytes\n",maxinum);
  22. 22 }

6.4.4 段地址对齐

可执行文件最终是要被操作系统装载运行的,这个装载的过程一般是通过虚拟内存页映射机制完成。在映射过程中,页是映射的最小单位。

6.4.5 进程栈初始化

进程刚开始启动的时候,须知道一些进程运行的环境,最基本的就是系统环境变量和进程的运行参数,很常见的一种做法是操作系统在进程启动前将这些信息提前保存到进程的虚拟空间的栈中。可以看一下,进程地址空间中堆、栈等的分布:

./SectionMapping.elf &
[1] 59117
[root@tlinux /]# cat /proc/59117/maps
00400000-004bc000 r-xp 00000000 08:05 4456                               /SectionMapping.elf
006bb000-006be000 rw-p 000bb000 08:05 4456                               /SectionMapping.elf
006be000-006c0000 rw-p 00000000 00:00 0 
017e1000-01804000 rw-p 00000000 00:00 0                                  [heap]
7ffc12ce6000-7ffc12d07000 rw-p 00000000 00:00 0                          [stack]
7ffc12db7000-7ffc12db9000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

6.5 Linux内核装载ELF过程简介

首先在用户层面,bash进程会调用fork()系统调用创建一个新的进程,然后新的进程调用execve()系统调用执行指定的ELF文件,原先的bash进程继续返回等待刚才启动的新进程结束,然后继续等待用户输入命令。

binary hacks读数笔记(装载)的更多相关文章

  1. binary hacks读数笔记(dlopen、dlsym、dlerror、dlclose)

    1.dlopen是一个强大的库函数.该函数将打开一个动态库,并把它装入内存.该函数主要用来加载库中的符号,这些符号在编译的时候是不知道的.比如 Apache Web 服务器利用这个函数在运行过程中加载 ...

  2. binary hacks读数笔记(readelf基本命令)

    一.首先对readelf常用的参数进行简单说明: readelf命令是Linux下的分析ELF文件的命令,这个命令在分析ELF文件格式时非常有用,下面以ELF格式可执行文件test为例详细介绍: 1. ...

  3. binary hacks读数笔记(ld 链接讲解 二)

    这块将介绍一下ld链接命令的具体使用.ld的作用:ld是GNU binutils工具集中的一个,是众多Linkers(链接器)的一种.完成的功能自然也就是链接器的基本功能:把各种目标文件和库文件链接起 ...

  4. binary hacks读数笔记(ld 链接讲解 一)

    首先我们先看两段代码: a.c extern int shared; int main(){ int a=100; swap(&a,&shared); } b.c int shared ...

  5. binary hacks读数笔记(堆、栈 VMA的分布)

    一.首先看一个简单的程序: #include<stdlib.h> int main() { while(1) { sleep(1000); } return 0; } gcc -stati ...

  6. binary hacks读数笔记(共享库)

    共享库从文件结构上来讲,与共享对象没什么区别.Linux下,共享库就是普通的ELF共享对象. 1.共享库命名: libname.so.x.y.z :其中最前面使用前缀lib,中间是库的名字和后缀&qu ...

  7. binary hacks读数笔记(nm命令)

    nm命令(names):输出包含三个部分:1 符号值.默认显示十六进制,也可以指定: 2 符号类型.小写表示是本地符号,大写表示全局符号(external); 3 符号名称. 例如:nm Simple ...

  8. binary hacks读数笔记(readelf命令)

    可以用readelf命令来查看elf文件内容,跟objdump相比,这个命令更详细. 1. readelf -h SimpleSection.o ELF Header: Magic: 7f 45 4c ...

  9. binary hacks读数笔记(objdump命令)

    一.首先看一下几个常用参数的基本含义: objdump命令是Linux下的反汇编目标文件或者可执行文件的命令,它还有其他作用,下面以ELF格式可执行文件test为例详细介绍: 1.objdump -f ...

随机推荐

  1. 网易新闻精彩评论集合(慢慢收集ing)

    一.刚才在停车场看一男的开个Q7,怎么也停不进去.我迅速把车停好要过去帮忙,他死活不同意.我说,你刚也看见了我的停车技术了,肯定不能给你刮了.他干脆把窗户摇上了.如今的社会啊,人与人的互信程度为什么就 ...

  2. MeteoInfoLab脚本示例:Maskout图形

    Maskout通常有两种类型:Maskout图形和Maskout数据.这里是Maskout图形的示例.需要用shaperead读取地图数据形成图层作为Maskout图层(这里是中国的行政区域china ...

  3. IDEA SpringBoot+JPA+MySql+Redis+RabbitMQ 秒杀系统

    先放上github地址:spike-system,可以直接下载完整项目运行测试 SpringBoot+JPA+MySql+Redis+RabbitMQ 秒杀系统 技术栈:SpringBoot, MyS ...

  4. Elasticsearch修改字段类型 (_reindex)

    1.设置索引t2为想要的数据类型 2.将t1 reindex到t2 3.数据reindex完成删除t1 4.设置索引t1为想要的数据类型 5.将t2 reindex到t1 如果 _reindex 超时 ...

  5. python接口测试之日志功能

    之前在简书中看了一篇关于日志功能的文档,供大家参考:https://www.jianshu.com/p/62f7b49b41e7 Python通过logging模块提供日志功能,所以直接导入即可 im ...

  6. 【线段树分治】Dash Speed

    代码的美妙 #include <bits/stdc++.h> %:pragma GCC optimize(3) using namespace std; const int maxn=7e ...

  7. UVA 10635 Prince and Princess—— 求LCS(最长公共子序列)转换成 求LIS(最长递增子序列)

    题目大意:有n*n个方格,王子有一条走法,依次经过m个格子,公主有一种走法,依次经过n个格子(不会重复走),问他们删去一些步数后,重叠步数的最大值. 显然是一个LCS,我一看到就高高兴兴的打了个板子上 ...

  8. utf-8和utf-8-sig的区别

    前言:在写入csv文件中,出现了乱码的问题. 解决:utf-8 改为utf-8-sig 区别如下: 1."utf-8" 是以字节为编码单元,它的字节顺序在所有系统中都是一样的,没有 ...

  9. mysql中事件失效如何解决

    重启Mysql服务可能会导致event_scheduler关闭,事件失效.解决方法如下: 1.解决办法: #查看是否开启 show variables like 'event_scheduler'; ...

  10. Android adb实现原理

    adb定义: adb(Android Debug Bridge) 安卓调试桥,包含adb client.adb server和adbd三部分. adb client:运行在PC上,即DDMS或者在Wi ...