进程虚拟地址空间之数据分区存放【转】
转自:http://blog.csdn.net/bullbat/article/details/7318269
作者:bullbat
在前面的《对一个程序在内存中的分析 》中很好的描述了程序在内存中的布局,这里对这个结果做些总结和实验验证。下面以Linux为例(实验结果显示windows上的结果也一样)。
我们还是利用前面看到过的这个图,如下图:32位X86机器的内存布局图,内存主要分为栈、堆、BSS段、数据段、代码段5个段。
代码段:代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。程序在被载入内存后,会被分为很多小的区(section),有一个rodata区(也就是常量区),常量区的数据内存中的位置为代码段,存放的数据为字符串、const修饰的常量(全局的或是静态的,局部的存放在栈中)、如Char* s=”Hello,World”,那么指针s所指向的字符串”Hello,World“存放在rodata区,而这个字符串的地址也就是指针s存放在数据段中(程序载入内存中为.data区)。再如,static char *const s=”hello,world";那么这时候不仅"hello,world"字符串存放在rodata区,指针s也同样。
数据段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量或者静态变量的一块内存区域。数据段属于静态内存分配。原则上数据段所对应的内存区数据是可以改变的。这里没有提到局部变量,这是因为局部变量一般都存放在栈中。局部变量不管是否有const修饰都存放在栈中,例如char *const lcp="999";字符窜"999"存放在代码段的rodata区,这个没有说的,而它对应的地址lcp指针存放在栈中;
BSS段:BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。例如全局变量int i;静态变量static int si;都存放在这里面。
堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减),要注意的是,当分配的数据大小操作内核的限制时,内核采用匿名映射的方式实现而不是从堆中分配内存。
栈(stack):栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段或代码段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。
在进程被载入内存中时,基本上被分裂成许多小的节(section)。我们比较关注的是几个主要的节:
(1) .text 节
.text 节基本上相当于二进制可执行文件的.text部分,它包含了完成程序任务的机器指令。
该节标记为只读,如果发生写操作,会造成segmentation fault。在进程最初被加载到内存中开始,该节的大小就被固定。
(2).data 节
.data节用来存储初始化过的变量,如:全局int a =0 ; 该节的大小在运行时固定的。
(3).bss 节
栈下节(below stack section ,即.bss)用来存储为初始化的变量,如:int a; 该节的大小在运行时固定的。
(4) 堆节
堆节(heap section)用来存储动态分配的变量,位置从内存的低地址向高地址增长。内存的分配和释放通过malloc() 和 free() 函数控制。
(5) 栈节
栈节(stack section)用来跟踪函数调用(可能是递归的),在大多数系统上从内存的高地址向低地址增长。
同时,栈这种增长方式,导致了缓冲区溢出的可能性。
(6).rodata节
常量区,全局或静态const变量、指针存放区。
(7)环境/参数节
环境/参数节(environment/arguments section)用来存储系统环境变量的一份复制文件,
进程在运行时可能需要。例如,运行中的进程,可以通过环境变量来访问路径、shell 名称、主机名等信息。
该节是可写的,因此在格式串(format string)和缓冲区溢出(buffer overflow)攻击中都可以使用该节。
另外,命令行参数也保持在该区域中。
我们以Linux为例,看下面的代码:
- <span style="font-size:18px;">#include <stdio.h>
- #include <stdlib.h>
- /*全局已初始化数组,存放在数据段中*/
- char a[100]="22222222";
- /*全局未初始化数组,存放在BSS段*/
- int b[100];
- /*全局未初始化数据,存放在BSS段*/
- int c;
- /*全局已初始化字符串指针,字符串存放在代码段的.rodata区,他的地址p存放在数据段中*/
- char * p="11111111";
- /*全局已初始化整形常量,存放在代码段的.rodata区*/
- const int ci=9;
- /*全局已初始化指针常量,字符创和他的地址cp都存放在代码段的.rodata区*/
- char *const cp="88888";
- int main()
- {
- /*局部未初始化数据,存放在栈中*/
- int li;
- /*局部已初始化字符串指针,字符串内容存放在代码段的.rodata区,他的地址lp存放在栈*/
- char *lp="66666";
- /*局部已初始化数据,存放在栈中*/
- char la[100]="4444444";
- /*静态未初始化数据,存放在BSS段*/
- static int si;
- /*静态已初始化数据,存放在数据段在数据段中*/
- static int sii=5;
- /*局部常量存放在在栈中*/
- const int lci=2;
- /*局部已初始化字符串指针常量,字符串存放在代码段的.rodata区,他的地址lcp存放在栈中*/
- char *const lcp="89999";
- /*静态已初始化字符串指针常量,字符串和他的地址scp都存放在代码段的.rodata区*/
- static char *const scp="kkkkk";
- /*从堆中申请内存,mc存放在栈中*/
- char *mc=(char*)malloc(100);
- /*函数的参数以栈的形式传入,前面的字符串从代码段的.rodata区获得*/
- printf("a[0]:%c\np[0]:%c\nla[0]:%c\nlp[0]:%c\n",a[0],p[0],la[0],lp[0]);
- /*参数为字符串常量,从代码段的.rodata区取出*/
- printf("hello world\n");
- return 1;
- }</span>
为验证我们在代码中的注释,我们对上面代码在fedora12上编译:
gcc -S segment.c
上面编译命令生成segment.c对应的汇编代码:
.file "segment.c"
;a为全局.data区
.globl a
.data
.align 32
.type a, @object
.size a, 100
a:
.string "22222222"
.zero 91
;b和c为.comm也就是BSS区
.comm b,400,32
.comm c,4,4
.globl p
.section .rodata
;全局p内容,即字符串为.rodata区
.LC0:
.string "11111111"
;全局p指针,即字符串的地址为.data区
.data
.align 4
.type p, @object
.size p, 4
p:
.long .LC0
.globl ci
.section .rodata
.align 4
;全局变量ci位于.rodata区
.type ci, @object
.size ci, 4
ci:
.long 9
;全局cp内容和地址都位于.rodata区
.globl cp
.LC1:
.string "88888"
.align 4
.type cp, @object
.size cp, 4
cp:
.long .LC1
;下面为代码中的其余字符串常量,位于.rodata区
.LC2:
.string "66666"
.LC3:
.string "89999"
.align 4
.LC4:
.string "a[0]:%c\np[0]:%c\nla[0]:%c\nlp[0]:%c\n"
.LC5:
.string "hello world"
;.text区,主要存放代码
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
pushl %edi
pushl %esi
pushl %ebx
subl $164, %esp
movl $.LC2, 144(%esp)
movl $875836468, 40(%esp)
movl $3421236, 44(%esp)
leal 48(%esp), %ebx
movl $0, %eax
movl $23, %edx
movl %ebx, %edi
movl %edx, %ecx
rep stosl
movl $2, 148(%esp)
movl $.LC3, 152(%esp)
movl $100, (%esp)
call malloc
movl %eax, 156(%esp)
movl 144(%esp), %eax
movzbl (%eax), %eax
movsbl %al, %esi
movzbl 40(%esp), %eax
movsbl %al, %ebx
movl p, %eax
movzbl (%eax), %eax
movsbl %al, %ecx
movzbl a, %eax
movsbl %al, %edx
movl $.LC4, %eax
movl %esi, 16(%esp)
movl %ebx, 12(%esp)
movl %ecx, 8(%esp)
movl %edx, 4(%esp)
movl %eax, (%esp)
call printf
movl $.LC5, (%esp)
call puts
movl $1, %eax
addl $164, %esp
popl %ebx
popl %esi
popl %edi
movl %ebp, %esp
popl %ebp
ret
.size main, .-main
.section .rodata
.LC6:
;scp和他的内容都放在.rodata
.string "kkkkk"
.align 4
.type scp.2177, @object
.size scp.2177, 4
scp.2177:
.long .LC6
.data
;sii存放在.data
.align 4
.type sii.2174, @object
.size sii.2174, 4
sii.2174:
.long 5
;局部si放在.comm
.local si.2173
.comm si.2173,4,4
.ident "GCC: (GNU) 4.4.2 20091027 (Red Hat 4.4.2-7)"
.section .note.GNU-stack,"",@progbits
对应上面的C代码,不难看出,和我们预想的完全一样,其中关键的地方我用不用的颜色标出来了,从汇编代码中我们同样可以看出,存放于栈中的局部数据其变量并没有出现在汇编代码中,是的,因为他直接存在了栈中,在程序中直接从栈中获得就没有必要用变量来访问了。
我们把这段代码最终还是编译成ELF可执行文件segment.out,然后我们用下面的命令看一下该可自行文件装入内存后的区域划分:
objdump -D segment.out > obj.txt
生成的内容放在了obj.txt文件中,该文件很长,不过我们只看这里我们关心的几个地方,如下:
segment.out: file format elf32-i386
Disassembly of section .interp:
/*我们调用的函数放在这里*/
Disassembly of section .plt:
08048304 <__gmon_start__@plt-0x10>:
8048304: ff 35 80 97 04 08 pushl 0x8049780
804830a: ff 25 84 97 04 08 jmp *0x8049784
8048310: 00 00 add %al,(%eax)
...
08048314 <__gmon_start__@plt>:
8048314: ff 25 88 97 04 08 jmp *0x8049788
804831a: 68 00 00 00 00 push $0x0
804831f: e9 e0 ff ff ff jmp 8048304 <_init+0x30>
08048324 <__libc_start_main@plt>:
8048324: ff 25 8c 97 04 08 jmp *0x804978c
804832a: 68 08 00 00 00 push $0x8
804832f: e9 d0 ff ff ff jmp 8048304 <_init+0x30>
08048334 <printf@plt>:
8048334: ff 25 90 97 04 08 jmp *0x8049790
804833a: 68 10 00 00 00 push $0x10
804833f: e9 c0 ff ff ff jmp 8048304 <_init+0x30>
08048344 <malloc@plt>:
8048344: ff 25 94 97 04 08 jmp *0x8049794
804834a: 68 18 00 00 00 push $0x18
804834f: e9 b0 ff ff ff jmp 8048304 <_init+0x30>
08048354 <puts@plt>:
8048354: ff 25 98 97 04 08 jmp *0x8049798
804835a: 68 20 00 00 00 push $0x20
804835f: e9 a0 ff ff ff jmp 8048304 <_init+0x30>
/*.text段,这里放的是具体的代码*/
Disassembly of section .text:
08048370 <_start>:
...
08048424 <main>:
...
Disassembly of section .fini:
0804858c <_fini>:
...
/*.rodata区,存放只读数据*/
Disassembly of section .rodata:
080485a8 <_fp_hw>:
80485a8: 03 00 add (%eax),%eax
...
080485ac <_IO_stdin_used>:
80485ac: 01 00 add %eax,(%eax)
80485ae: 02 00 add (%eax),%al
080485b0 <__dso_handle>:
80485b0: 00 00 add %al,(%eax)
80485b2: 00 00 add %al,(%eax)
80485b4: 31 31 xor %esi,(%ecx)
80485b6: 31 31 xor %esi,(%ecx)
80485b8: 31 31 xor %esi,(%ecx)
80485ba: 31 31 xor %esi,(%ecx)
80485bc: 00 00 add %al,(%eax)
...
080485c0 <ci>:
80485c0: 09 00 or %eax,(%eax)
80485c2: 00 00 add %al,(%eax)
80485c4: 38 38 cmp %bh,(%eax)
80485c6: 38 38 cmp %bh,(%eax)
80485c8: 38 00 cmp %al,(%eax)
...
080485cc <cp>:
80485cc: c4 85 04 08 36 36 les 0x36360804(%ebp),%eax
80485d2: 36 36 36 00 38 add %bh,%ss:(%eax)
80485d7: 39 39 cmp %edi,(%ecx)
80485d9: 39 39 cmp %edi,(%ecx)
80485db: 00 61 5b add %ah,0x5b(%ecx)
80485de: 30 5d 3a xor %bl,0x3a(%ebp)
80485e1: 25 63 0a 70 5b and $0x5b700a63,%eax
80485e6: 30 5d 3a xor %bl,0x3a(%ebp)
80485e9: 25 63 0a 6c 61 and $0x616c0a63,%eax
80485ee: 5b pop %ebx
80485ef: 30 5d 3a xor %bl,0x3a(%ebp)
80485f2: 25 63 0a 6c 70 and $0x706c0a63,%eax
80485f7: 5b pop %ebx
80485f8: 30 5d 3a xor %bl,0x3a(%ebp)
80485fb: 25 63 0a 00 68 and $0x68000a63,%eax
8048600: 65 gs
8048601: 6c insb (%dx),%es:(%edi)
8048602: 6c insb (%dx),%es:(%edi)
8048603: 6f outsl %ds:(%esi),(%dx)
8048604: 20 77 6f and %dh,0x6f(%edi)
8048607: 72 6c jb 8048675 <scp.2177+0x61>
8048609: 64 00 6b 6b add %ch,%fs:0x6b(%ebx)
804860d: 6b 6b 6b 00 imul $0x0,0x6b(%ebx),%ebp
8048611: 00 00 add %al,(%eax)
...
08048614 <scp.2177>:
8048614: 0b .byte 0xb
8048615: 86 04 08 xchg %al,(%eax,%ecx,1)
Disassembly of section .eh_frame_hdr:
...
/*.data区,存放已初始化的全局和静态数据*/
Disassembly of section .data:
080497a0 <__data_start>:
...
080497c0 <a>:
80497c0: 32 32 xor (%edx),%dh
80497c2: 32 32 xor (%edx),%dh
80497c4: 32 32 xor (%edx),%dh
80497c6: 32 32 xor (%edx),%dh
...
08049824 <p>:
8049824: b4 85 mov $0x85,%ah
8049826: 04 08 add $0x8,%al
08049828 <sii.2174>:
8049828: 05 .byte 0x5
8049829: 00 00 add %al,(%eax)
...
/*.bss区,存放未初始化的全局和静态变量*/
Disassembly of section .bss:
08049840 <completed.5934>:
8049840: 00 00 add %al,(%eax)
...
08049844 <dtor_idx.5936>:
8049844: 00 00 add %al,(%eax)
...
08049848 <si.2173>:
...
08049860 <b>:
...
080499f0 <c>:
80499f0: 00 00 add %al,(%eax)
...
Disassembly of section .comment:
...
从不同颜色标出数据可以很清楚的看出,上面的的数据和前面我们分析的汇编代码完全一致。Windwos的情况类似,可以在工程->设置->C/C++分类中设置为Listing Files然后在列表中选择Assembly with Source Code,这样在debug或release文件夹下会生成C对应的汇编代码。当然也可以在调试的过程中直接看其每条C语句对应的汇编代码。
本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sky-heaven/p/5659512.html,如需转载请自行联系原作者
进程虚拟地址空间之数据分区存放【转】相关推荐
- 【读书笔记】【程序员的自我修养 -- 链接、装载与库(二)】进程虚拟地址空间、装载与动态链接、GOT、全局符号表、共享库的组织、DLL、C++与动态链接
文章目录 前言 介绍 可执行文件的装载与进程 进程虚拟地址空间 装载方式 操作系统对可执行文件的装载 进程虚存空间分布 ELF文件的链接视图和执行视图 堆和栈 Linux 内核装载ELF & ...
- Linux进程虚拟地址空间
1. 前言 谈到Linux进程虚拟地址空间,还要从程序说起.本文通过分析程序的编译执行过程,分享了Linux进程虚拟地址空间的结构.组织和创建,并通过分析Linux内核源代码,总结了进程.进程虚拟地址 ...
- Linux虚拟内存和进程虚拟地址空间简述
后台开发经常会问此类问题,虽说难度不大,但是知道和不知道还是有区别的.以下的内容总结自<深入理解Linux内核>第一章,仅仅是简述,没有深入研究,毕竟内存管理这一块内容超级多,感兴趣的同学 ...
- 2.4父子进程虚拟地址空间情况
内核区中,父进程和子进程的pid是不同的. 定义的局部变量pid在栈空间中,父子进程中栈空间中的pid不同,在父进程中为子进程的进程号,在子进程中为0 实际上,更准确来说,linux的fork()是通 ...
- 【Linux】进程虚拟地址空间
进程虚拟地址空间打破了我一直以来对于程序地址空间的认识,它真的好神奇. 我们首先来看一下下面这段代码: 1 #include<stdio.h>2 #include<unistd.h& ...
- 父子进程虚拟地址空间情况
父子进程虚拟地址空间情况 笔记来源于牛客网<Linux多进程开发> The child process and the parent process run in separate mem ...
- 虚拟地址,虚拟地址空间, 交换分区
1.虚拟内存是内存管理的一种方式, 它在磁盘上划分出一块空间由操作系统管理,当物理内存耗尽是充当物理内存来使用.它将多个物理内存碎片和部分磁盘空间重定义为连续的地址空间,以此让程序认为自己拥有连续可用 ...
- linux和windows的进程的虚拟地址空间
昨晚看到了深夜,终于对进程的虚拟地址空间有了个大致的了解,很激动,也很欣慰.回头想来,一个程序员,真的应该知道这些知识,否则还真不太称职. 首先告诉大家,我后面提到的这些知识在<windows核 ...
- 【Linux系统编程】进程地址空间和虚拟地址空间
00. 目录 文章目录 00. 目录 01. 早期的内存分配机制 02. 分段 03. 分页 04. 地址比较 05. 附录 01. 早期的内存分配机制 在早期的计算机中,要运行一个程序,会把这些程序 ...
最新文章
- Ubuntu 18.04 Authentication Error
- 程序人生:这5个程序员,改变了世界,你都认识吗!
- oracle数据库dblink创建语句_「运维实验」——达梦数据库DBlink连接Oracle配置
- Spring Boot读取application.yaml属性
- elementary os java,吐槽ELEMENTARY OS系统/ELEMENTARY OS系列文章汇总
- 巨蟒django之CRM2 展示客户列表分页
- SpringAOP底层API之代理对象执行流程
- 数据库、数据库系统、数据库管理系统三者的区别
- VS2008中关于“加载安装组件时遇到问题。取消安装”的解决办法
- SAP的系统审计以及SM19的使用
- 在线安装提示失败显示系统镜像MD5不正确怎么办
- c语言中 让小球 发射小球,小球发射问题求大神解决(让小球向鼠标蓄力方向发射)...
- 编写大并发高负载通讯程序
- 不能用来修饰interface的有
- 福州大学计算机考研-一位二战考生的点点滴滴(连载1/5)
- TVD$XTAT在linux下安装使用详解
- JAVA毕业设计计算机专业招聘网站计算机源码+lw文档+系统+调试部署+数据库
- raid卡缓存对硬盘性能_我们怎么解决机械硬盘既慢又容易坏的问题?
- 蝉知网站模板常用的Jquery
- 聊一聊C语言变量的含义
热门文章
- django mysql 初始化_Django初始化基础(1)
- html5 ul下的li重叠解决,html – 如何仅在嵌套的ul中悬停当前的li?
- promise用法_JavaScript中的async/await的用法和理解
- python 图形_Python切分图像小案例(1、3、2、4象限子图互换)
- 2020年市场最缺什么_2020年资本市场回顾
- linux恢复fat文件系统,使用‘fsck’修复Linux中文件系统错误的方法
- IDEA中导入一个新项目,出现了Cannot resolve symbol 'String'
- python 命令模式_python 设计模式之命令模式
- 为什么我加了过滤器然后就登不进去了_布隆过滤器过时了,未来属于布谷鸟过滤器?...
- 云服务器选ssd还是hdd_SSD和普通硬盘对比?SSD到底好不好?看超变态测试