谈谈程序在内存中的分布

转载 2013年12月04日 19:11:38

原文地址:http://blog.csdn.net/high_high/article/details/7202233


作为一个菜鸟,这个题目有点大,所以这篇博客缺点是可能不够深入,但应该还是很详细的,希望能对大家有所帮助。

1.简介加初步分析

在linux系统中,程序在内存中的分布如下所示:

低地址 .text .data .bss             heap(堆)      -->       unused    <--      stack(栈)       env 高地址

其中 :

.text 部分是编译后程序的主体,也就是程序的机器指令。

.data 和 .bss 保存了程序的全局变量,.data保存有初始化的全局变量,.bss保存只有声明没有初始化的全局变量。

heap(堆)中保存程序中动态分配的内存,比如C的malloc申请的内存,或者C++中new申请的内存。堆向高地址方向增长。

stack(栈)用来进行函数调用,保存函数参数,临时变量,返回地址等。

下面是测试用的程序,比较简单,用来输出各个变量的地址。

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3.   
  4. int ug;  
  5. int dg = 1;  
  6.   
  7. void func(int);  
  8. void func2(int);  
  9. int main(int argc, char ** argv){  
  10.   int ul;  
  11.   int dl = 2;  
  12.   int *pi = (int *)malloc(sizeof(int));  
  13.   *pi = 4;  
  14.   int *pi2 = (int *)malloc(sizeof(int));  
  15.   *pi2 = 8;  
  16.   
  17.   printf("address of main:     %x\n", main);  
  18.   printf("undefined global %d: %x\n", ug, &ug);  
  19.   printf("defined global %d:   %x\n", dg, &dg);  
  20.   printf("undefined local %d:  %x\n", ul, &ul);  
  21.   printf("defined local %d:    %x\n", dl, &dl);  
  22.   printf("address of func:     %x\n", func);  
  23.   func(32);  
  24.   printf("dynamic alloc %d:    %x\n", *pi, pi);  
  25.   printf("dynamic alloc %d:    %x\n", *pi2, pi2);  
  26.   
  27.   free(pi);  
  28.   free(pi2);  
  29.   
  30.   int a;  
  31.   scanf("%d", &a);  
  32.   
  33.   return 0;  
  34. }  
  35.   
  36. void func(int arg){  
  37.   int uloc;  
  38.   int dloc = 16;  
  39.   printf("address of argument %d: %x\n", arg, &arg);  
  40.   printf("undefined func local %d: %x\n", uloc, &uloc);  
  41.   printf("defined func local %d: %x\n", dloc, &dloc);  
  42.   func2();  
  43. }  
  44.   
  45. void func2(){  
  46.   int loc = 64;  
  47.   printf("local of func2 %d: %x\n", loc, &loc);  
  48. }  

程序输出如下:

[plain] view plaincopy
  1. address of main:     4005f4  
  2. undefined global 0: 601050  
  3. defined global 1:   601038  
  4. undefined local 32767:  c2b96484  
  5. defined local 2:    c2b96488  
  6. address of func:     40075a  
  7. address of argument 32: c2b9643c  
  8. undefined func local -451161400: c2b96448  
  9. defined func local 16: c2b9644c  
  10. local of func2 64: c2b9641c  
  11. dynamic alloc 4:    16b1010  
  12. dynamic alloc 8:    16b1030  

2.使用进程maps文件深入分析

在linux下,可以查看进程的maps文件了解程序在内存中的分布,上面那个程序运行后的进程的maps文件内容如下:

[plain] view plaincopy
  1. cat /proc/2506/maps   
  2. 00400000-00401000 r-xp 00000000 08:03 4080116                            /home/yuduo/Workspace/C/sandbox/address.o  
  3. 00600000-00601000 r--p 00000000 08:03 4080116                            /home/yuduo/Workspace/C/sandbox/address.o  
  4. 00601000-00602000 rw-p 00001000 08:03 4080116                            /home/yuduo/Workspace/C/sandbox/address.o  
  5. 016b1000-016d2000 rw-p 00000000 00:00 0                                  [heap]  
  6. 7fc8e4bfc000-7fc8e4d91000 r-xp 00000000 08:03 4460234                    /lib/x86_64-linux-gnu/libc-2.13.so  
  7. 7fc8e4d91000-7fc8e4f90000 ---p 00195000 08:03 4460234                    /lib/x86_64-linux-gnu/libc-2.13.so  
  8. 7fc8e4f90000-7fc8e4f94000 r--p 00194000 08:03 4460234                    /lib/x86_64-linux-gnu/libc-2.13.so  
  9. 7fc8e4f94000-7fc8e4f95000 rw-p 00198000 08:03 4460234                    /lib/x86_64-linux-gnu/libc-2.13.so  
  10. 7fc8e4f95000-7fc8e4f9b000 rw-p 00000000 00:00 0   
  11. 7fc8e4f9b000-7fc8e4fbc000 r-xp 00000000 08:03 4460221                    /lib/x86_64-linux-gnu/ld-2.13.so  
  12. 7fc8e5199000-7fc8e519c000 rw-p 00000000 00:00 0   
  13. 7fc8e51b7000-7fc8e51bb000 rw-p 00000000 00:00 0   
  14. 7fc8e51bb000-7fc8e51bc000 r--p 00020000 08:03 4460221                    /lib/x86_64-linux-gnu/ld-2.13.so  
  15. 7fc8e51bc000-7fc8e51be000 rw-p 00021000 08:03 4460221                    /lib/x86_64-linux-gnu/ld-2.13.so  
  16. 7fffc2b76000-7fffc2b97000 rw-p 00000000 00:00 0                          [stack]  
  17. 7fffc2bbe000-7fffc2bbf000 r-xp 00000000 00:00 0                          [vdso]  
  18. ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]  

可以看到程序的.text的内存是:00400000-00401000,main函数和func函数的地址都在这个范围内(4005f4、40075a),可以看到这部分内存权限是可执行(r-xp),这里面的代码也确实是需要执行的。写这篇博客时我又发现这段内存刚好一个页面大小(4K),有趣。

.data的内存是:00601000-00602000,因为两个全局变量全在这里(601050、601038),从权限也可以看出来(rw-p),这里w代表可写,上面那部分内存(00600000-00601000)权限是 r--p,估计是用来保存常量(const)的。

然后就是堆(heap)了,地址范围是016b1000-016d2000,两个动态分配的变量刚好在这个范围里面:16b1010、16b1030,从他们的地址可以看出来他们是向高地址增长的。

堆后面直接就是高地址了,首先是一些动态链接库,动态链接库在内存中的位置在每个系统上都不一样,有些系统放在.text前面,这个无所谓了,不关心。

然后就是栈(stack)了,地址范围7fffc2b76000-7fffc2b97000。例子里面很多变量都在这个范围内,main的两个局部变量(c2b96484、c2b96488),func的参数和两个局部变量(c2b9643c、c2b96448、c2b9644c),func2的局部变量(c2b9641c)。从这里也可以看出栈是向低地址增长的,因为我们确定函数调用顺序是main->func->func2,所以压栈顺序也一定是这个,从每个函数中找个代表出来按压栈顺序排列,c2b96484->c2b9643c->c2b9641c,发现地址越来越小了,所以栈向低地址增长没有问题。

还又个问题,我们看到main里面两个局部变量,先声明的地址小(c2b96484),后声明的地址大(c2b96488),其实这并不违背栈向低地址增长,因为在main函数这个栈帧里面(stack frame),保存局部变量并没有压栈出栈等栈的操作,完全是两码事,比如我们看一下汇编代码,可以发现这局部变量是这样赋值的:

[plain] view plaincopy
  1. movl    $2, -8(%rbp)  

只和基地址有关(rbp)。我个人觉得局部变量地址和编译器有关,但是没有测试,提出来算个想法吧 :)

stack后面还有两个段:vdso,不知道是什么;vsyscall,内核的代码,每个程序都少不了。

顺便再说下,上面的测试还可以看出全局变量没初始化会默认赋值为0,而局部变量不会,所以局部变量使用前一定要初始化,否则会出现不知道的结果。

3. 使用objdump再深入分析

使用命令objdump -d address.o显示程序的汇编内容,因为这个命令的输出是在是太详细,所以不能把结果都贴出来,但是并不影响大家理解。大家也可以在自己电脑上试试,再和后面内容对应起来看。如果还是有疑问,就给我留言吧:)
先把objdump输出的开头部分贴出来:
[plain] view plaincopy
  1. objdump -d address.o  
  2.   
  3. address.o:     file format elf64-x86-64  
  4.   
  5.   
  6. Disassembly of section .init:  
  7.   
  8. 0000000000400498 <_init>:  
  9.   400498:   48 83 ec 08             sub    $0x8,%rsp  
  10.   40049c:   e8 9b 00 00 00          callq  40053c <call_gmon_start>  
  11.   4004a1:   e8 2a 01 00 00          callq  4005d0 <frame_dummy>  
  12.   4004a6:   e8 f5 03 00 00          callq  4008a0 <__do_global_ctors_aux>  
  13.   4004ab:   48 83 c4 08             add    $0x8,%rsp  
  14.   4004af:   c3                      retq     

首先说我的文件格式是elf64-x86-64的,然后是.init的反汇编(从机器码生成汇编码),后面还有很多程序开始运行后main函数调用之前的很多初始化工作,这些都是编译器和操作系统加的,不要以为程序开始运行后就直接开始执行main哦。不过这里关心的还是main,main的反汇编部分如下:
  1. 00000000004005f4 <main>:  
  2.   4005f4:   55                      push   %rbp  
  3.   4005f5:   48 89 e5                mov    %rsp,%rbp  
  4.   4005f8:   48 83 ec 30             sub    $0x30,%rsp  
  5.   4005fc:   89 7d dc                mov    %edi,-0x24(%rbp)  
  6.   4005ff:   48 89 75 d0             mov    %rsi,-0x30(%rbp)  
  7.   400603:   c7 45 f8 02 00 00 00    movl   $0x2,-0x8(%rbp)  
  8.   40060a:   bf 04 00 00 00          mov    $0x4,%edi  
  9.   40060f:   e8 dc fe ff ff          callq  4004f0 <malloc@plt>  

可以看到main的起始地址是4005f4,和第一部分里面结果一样。

关于地址后面的内容我再解释以下吧,55是机器码,对应汇编码push %rbp,因为55只有一个字节,所以后面的地址是4005f5,下一个机器码是48 89 e5,对应的汇编是:mov %rsp, %rbp,然后就以此类推了。不同的汇编对应的机器码的字节数是不同的,所以不要惊讶机器码为什么参差不齐的。再贴一部分func的反汇编(beautiful)吧:

[plain] view plaincopy
  1. 000000000040075a <func>:  
  2.   40075a:   55                      push   %rbp  
  3.   40075b:   48 89 e5                mov    %rsp,%rbp  
  4.   40075e:   48 83 ec 20             sub    $0x20,%rsp  
  5.   400762:   89 7d ec                mov    %edi,-0x14(%rbp)  
  6.   400765:   c7 45 fc 10 00 00 00    movl   $0x10,-0x4(%rbp)  
  7.   40076c:   8b 4d ec                mov    -0x14(%rbp),%ecx  
  8.   40076f:   b8 9e 09 40 00          mov    $0x40099e,%eax  
  9.   400774:   48 8d 55 ec             lea    -0x14(%rbp),%rdx  
  10.   400778:   89 ce                   mov    %ecx,%esi  
  11.   40077a:   48 89 c7                mov    %rax,%rdi  
  12.   40077d:   b8 00 00 00 00          mov    $0x0,%eax  
  13.   400782:   e8 49 fd ff ff          callq  4004d0 <printf@plt>  

4. 使用gdb再再(装b)深入分析

深入到这个份上,其实已经没有什么好分析的了,只是顺便说说gdb下怎么检测变量和内存。
检查变量就是print了,比如:
[plain] view plaincopy
  1. (gdb) print main  
  2. $1 = {int (int, char **)} 0x4005f4 <main>  

可以看出main是个函数,起始地址0x4005f4,没有什么问题。下面重点介绍怎么检测内存。
gdb使用x检测内存,使用格式是x/FMT ADDRESS,其中FMT是想要重复的次数+格式化字符(format letter)+ 大小字符(size letter),ADDRESS不用说就是想要检测的地址了。
其中格式化字符有:o(octal   8进制), x(hex   16进制), d(decimal   10进制), u(unsigned decimal    无符号10进制), t(binary    2进制), f(float   浮点数), a(address    地址), i(instruction    指令), c(char    字符) 和 s(string   字符串).
大小字符有:b(byte   1个字节), h(halfword   2个字节), w(word   4个字节), g(giant, 8个字节)。
实际使用中格式化字符和大小字符位置貌似可以调换,我用的时候也不太在意。下面用三种方法检测从地址main(0x4005f4)开始的8个字节:
比如x/8xb main表示检测mian开始的内存,输出格式为16进制(x),每次一个字节(b),检测8次(8),输出如下:
[plain] view plaincopy
  1. (gdb) x/8xb main  
  2. 0x4005f4 <main>:  0x55    0x48    0x89    0xe5    0x48    0x83    0xec    0x30  

main的地址还是0x4005f4,然后可以回头看看第三部分里面main的反汇编,前8个字节就是 0x550x48 0x890xe5 0x480x83 0xec0x30。
使用x/8bx main效果是一样的,不过话说回来gcc的一套工具大多参数位置可以随便摆放。

再比如x/2xw main,检测main开始的内存,输出格式为16进制(x),每次4个字节,检测2次(2),输出如下:
[plain] view plaincopy
  1. (gdb) x/2xw main  
  2. 0x4005f4 <main>:  0xe5894855  0x30ec8348  

输出貌似和上面按字节输出有些不同了,4个4个倒序了,因为我的处理器是intel的,intel采用小头编码方式(little-endian),低地址的字节排在低位(十位、个位),高地址的字节排在高位(千位,万位),所以上面的0x55在低位,按4个字节输出就在最后面了(低位)。
我再换种方式输出可能会更清楚:x/1xg main,同样检测main开始的内存,输出格式还是16进制(x),不同的是每次8个字节(g),只检测一次:
[plain] view plaincopy
  1. (gdb) x/1xg main  
  2. 0x4005f4 <main>:  0x30ec8348e5894855  

这个分析就留给读者当小练习吧,如果不懂还是那句话,给我留言吧:)


参考文献:
Linux assembly language programming. Bob Neveln. 2000
The art of debugging with gdb, ddd, and eclipse. Norman Matloff, Peter Jay Salzman. 2008
Professional Linux kernel architecture. Wolfgang Mauerer. 2008

相关文章推荐

谈谈程序在内存中的分布

作为一个菜鸟,这个题目有点大,所以这篇博客缺点是可能不够深入,但应该还是很详细的,希望能对大家有所帮助。 1.简介加初步分析 在linux系统中,程序在内存中的分布如下所示: 低...

c/c++程序在内存中的分布

一个由C/C++编译的程序占用的内存分为以下几个部分 1、栈区(stack)— 程序运行时由编译器自动分配,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。程序结束时由编译器自动释...

程序的内存分布

图1 左边的是UNIX/LINUX系统的执行文件,右边是对应进程逻辑地址空间的划分情况。 1.堆栈区(stack),堆栈是由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方...

c程序内存分布

C程序一般分为1.程序段:程序段为程序代码在内存中的映射.一个程序可以在内存中多有个副本.2.初始化过的数据:在程序运行值初已经对变量进行初始化的3.未初始化过的数据:在程序运行初未对变量进行初始化的...

程序(进程)内存分布解析

在多任务操作系统中的每一个进程都运行在一个属于它自己的内存沙盘中。这个沙盘就是虚拟地址空间(virtual address space),在32位模式下它总是一个4GB的内存地址块。这些虚拟地址通...

程序在内存中的分布

堆和栈的区别 一、预备知识—程序的内存分配 一个由c/C++编译的程序占用的内存分为以下几个部分 1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类...

C语言中内存分布及程序运行中(BSS段、数据段、代码段、堆栈)

原文链接 BSS段:(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内...

程序在内存中的分布

BSS段:(bsssegment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文BlockStarted by Symbol的简称。BSS段属于静态内存分配。 数据段 :数据段...

C程序的内存分布

1. Text segment/Code segment (文本/代码段) 2. Initialized data segment (已初始化数据段) 3. Uninitialized data se...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)