博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

/proc/{pid}/maps解读, 虚拟内存

Posted on 2019-12-19 15:23  bw_0927  阅读(8803)  评论(0)    收藏  举报

 

/proc/self/maps

 

 

非常常用的系统文件

总共6列,如

76093000-76096000 r-xp 00000000 b3:19 941 /system/lib/libmemalloc.so

  1. 所处虚拟内存地址(VMA)范围:``76093000-76096000`

    • 在Linux中将进程虚拟空间中的一个段叫做虚拟内存区域VMA(Virtual Memory Area)。
    • VMA对应ELF文件中的segment
    • ELF文件有sectionsegment的概念。

从链接的角度看,ELF是按照section存储的,事实也的确如此;从装载的角度看,ELF文件又按照segment进行划分,这是为了防止按照section装载时造成的内部碎片

       segment相当于是将多个属性(读写执行)相同的section合并在一起进行。program headers 存放segment的信息;section table存放section的信息.

  1. VMA权限:r-xp

    r=读,w=写,x=,s=共享,p=私有

  2. 偏移量:00000000

    表示VMA对应的segment在映像文件中的偏移。

  3. 主设备号和次设备号(大雾):b3:19

  4. 映像文件的节点号inode:941

  5. 映像文件的路径:/system/lib/libmemalloc.so


https://blog.csdn.net/lijzheng/article/details/23618365
内核中进程的一段地址空间用一个vm_area_struct结构体表示,所有地址空间存储在task->mm->mmap链表中。
 

Vm_area_struct每项对应解析如下表所示:

内核每进程的vm_area_struct项

/proc/pid/maps中的项

含义

vm_start

“-”前一列,如00377000

此段虚拟地址空间起始地址

vm_end

“-”后一列,如00390000

此段虚拟地址空间结束地址

vm_flags

第三列,如r-xp

此段虚拟地址空间的属性。每种属性用一个字段表示,r表示可读,w表示可写,x表示可执行,p和s共用一个字段,互斥关系,p表示私有段,s表示共享段,如果没有相应权限,则用’-’代替

vm_pgoff

第四列,如00000000

对有名映射,表示此段虚拟内存起始地址在文件中以页为单位的偏移。对匿名映射,它等于0或者vm_start/PAGE_SIZE

vm_file->f_dentry->d_inode->i_sb->s_dev

第五列,如fd:00

映射文件所属设备号。对匿名映射来说,因为没有文件在磁盘上,所以没有设备号,始终为00:00。对有名映射来说,是映射的文件所在设备的设备号

vm_file->f_dentry->d_inode->i_ino

第六列,如9176473

映射文件所属节点号。对匿名映射来说,因为没有文件在磁盘上,所以没有节点号,始终为0。对有名映射来说,是映射的文件的节点号

 

第七列,如/lib/ld-2.5.so

对有名来说,是映射的文件名。对匿名映射来说,是此段虚拟内存在进程中的角色。[stack]表示在进程中作为栈使用,[heap]表示堆。其余情况则无显示

下面一起看下一个proc maps的例子。

 

r-xp  权限是只读,并且可执行,说明是应用程序的代码段

rw-p  权限是可读可写,但是没有执行权限,说明该段是pthread的数据段

 

 

堆[heap]段。

08c64000-08c85000 rw-p 08c64000 00:00 0          [heap]

有些maps文件并不会出现该记录,这主要跟程序中有无使用malloc相关,如果主线程使用了malloc就会有该记录,否则就没有。在子线程中调用malloc,会产生另外的堆映射,但是并不会标记[heap]。

 

 

栈段[stack]

栈(stack),作为进程的临时数据区,由kernel把匿名内存map到虚存空间,栈空间的增长方向是从高地址到低地址

 

bfd50000-bfd65000 rw-p bffea000 00:00 0          [stack]

 

对于单线程应用程序而言,只有一个[stack]段,对应多线程应用程序,[stack]段是主线程的栈空间,子线程的栈空间则用pthread库自动分配。

 

 例1,将一个单线程的应用的局部变量的地址打印出来,执行的结果如下所示:

 ./pthread2

tid addr 0xbfc73600

对应的maps文件:

08048000-08049000 r-xp 00000000 fd:00 3145811    /home/lijz/code/pthread2

08049000-0804a000 rw-p 00000000 fd:00 3145811    /home/lijz/code/pthread2

b7f7e000-b7f80000 rw-p b7f7e000 00:00 0

b7f8a000-b7f8b000 rw-p b7f8a000 00:00 0

bfc5f000-bfc74000 rw-p bffea000 00:00 0          [stack]

局部变量的地址0xbfc73600在[stack]区间。

 

例2:将一个拥有一个子线程的应用局部变量打印出来,执行的结果如下所示:

tid addr 0xbfd64740---------主线程中打印的局部变量地址

child thread run

stackaddr   0xb7fc93c4--------子线程中打印的局部变量地址

guardsize 4096---------栈保护页大小

 

对应的maps文件如下:

08048000-08049000 r-xp 00000000 fd:00 3145811    /home/lijz/code/pthread2

08049000-0804a000 rw-p 00000000 fd:00 3145811    /home/lijz/code/pthread2

08c64000-08c85000 rw-p 08c64000 00:00 0          [heap]

b75c9000-b75ca000 ---p b75c9000 00:00 0---------pthread_create默认的栈溢出保护区

b75ca000-b7fcc000 rw-p b75ca000 00:000------------pthread_create创建的子线程的栈空间

b7fd6000-b7fd7000 rw-p b7fd6000 00:00 0------------------4KB应该也是通过mmap产生的匿名映射

bfd50000-bfd65000 rw-p bffea000 00:00 0          [stack]---------主进程的栈空间

 

       由上执行结果显示,主线程中局部变量地址0xbfd64740落在[stack]区间,

子线程局部变量地址0xb7fc93c4则落在b75ca000-b7fcc000 rw-p b75ca00区间,并且局部变量的地址从高地址开始分配,说明该VMA正是子线程的栈地址空间。

另外,对栈空间,pthread默认设置了一个4KB的栈保护页,对应的区间为:b75c9000-b75ca000---p b75c9000,该区间不可读,不可写,也不能执行,通过这些属性信息的设置,可以达到栈溢出保护的作用。

 

例3:在例2的基础上,多创建一个线程,pthread2程序的执行结果如下所示:

./pthread2

tid addr 0xbfc81610----------主线程局部变量地址

child thread run

stackaddr = 0xb7f183c0-------子线程1局部变量地址

guardsize 4096

child thread2 run

stackaddr =0xb75173c4 ----------子线程局部变量地址

guardsize 4096

对应的maps文件:

08048000-08049000 r-xp 00000000 fd:00 3145811    /home/lijz/code/pthread2

08049000-0804a000 rw-p 00000000 fd:00 3145811    /home/lijz/code/pthread2

092d6000-092f7000 rw-p 092d6000 00:00 0          [heap]

76b16000-b6b17000 rw-p 76b16000 00:00 0 ----------mallocmmap

b6b17000-b6b18000 ---p b6b17000 00:00 0

b6b18000-b7518000 rw-p b6b18000 00:000---------pthread thread2 stack space

b7518000-b7519000 ---p b7518000 00:00 0                                      

b7519000-b7f1b000 rw-p b7519000 00:000----------pthread thread1 stack space

b7f25000-b7f26000 rw-p b7f25000 00:00 0

bfc6e000-bfc83000 rw-p bffea000 00:00 0          [stack]---main thread stack space

 

 从maps文件记录上看,增加一个子线程,在maps文件中就增加了两条记录,分别是子线程的栈空间和栈保护页的记录。

默认情况下,pthread为子线程预留的栈空间大小为1MB,栈保护页为4KB(这主要跟页大小相关)。

  总之,proc maps文件可以查看进程的内存映射,每一段内存的权限属性等信息。
————————————————

http://blog.coderhuo.tech/2017/10/12/Virtual_Memory_C_strings_proc/

三、虚拟内存

在计算机领域, 虚拟内存是通过软硬件结合实现的一种内存管理技术, 它将程序所使用的内存地址(虚拟内存地址)映射到计算机的物理内存上(物理内存地址),这使得每个程序看到的内存地址空间都是连续的(或是一些连续地址空间的集合)。

操作系统管理虚拟地址空间, 以及虚拟地址空间到物理内存的映射。CPU中的地址转换硬件(通常被称为内存管理单元, MMU)自动将虚拟内存地址转换成物理内存地址。操作系统可以提供比实际物理内存更多的虚拟内存,这一行为是通过操作系统中的软件来实现的。

虚拟内存的主要好处包含以下几点:

  • 将应用程序从内存管理中解放出来, 应用程序只需关心自己的逻辑
  • 不同应用程序间的虚拟内存是相互隔离的, 所以安全性增加了
  • 结合内存分页管理技术, 应用程序理论上可使用比物理内存更多的内存空间

有关虚拟内存的知识, 可进一步阅读维基百科上的相关介绍:虚拟内存

虚拟内存探究 – 第三篇:一步一步画虚拟内存图中,我们将探索虚拟内存的更多细节,并且看下虚拟内存中都有些什么, 以及这些东西分别位于虚拟内存的什么地方。

继续阅读本文前, 你需要知道以下几点:

  • 每个进程都有自己独立的虚拟内存
  • 虚拟内存大小依赖于计算机系统架构
  • 不同的操作系统对虚拟内存的处理会有所不同, 对于现代的大多数操作系统来说, 虚拟内存如下所示:

 

 

 

在虚拟内存的高地址空间,我们可以看到(下面仅列出了部分内容,并非全部):

  • 命令行参数和环境变量
  • “向下”生长的栈。咋看之下这是违反直觉的,但这确实是虚拟内存中栈的实现方式。

在虚拟内存的低地址空间, 我们可以看到:

  • 可执行程序(实际上远比这复杂,但对于理解本文剩余内容足够了)
  • “向上”生长的堆

堆是虚拟内存的一部分,动态分配的内存(比如用malloc分配的内存)位于堆中。

请时刻记住, 虚拟内存和物理内存是不同的

 

进程的虚拟内存空间多大

进程虚拟地址空间的大小依赖于计算机系统架构。我运行本例使用的是64位机器,所以理论上每个进程的虚拟内存是2^64字节,内存最高地址是0xffffffffffffffff (1.8446744e+19),最低地址是0x0

 

/proc/[pid]/mem
              This file can be used to access the pages of a process's memory
          through open(2), read(2), and lseek(2).

proc/[pid]/maps
              A  file containing the currently mapped memory regions and their access permissions.

五、替换进程的字符串

我们接下来要在一个进程的堆中搜索特定字符串,并用另一个字符串(长度不大于原字符串)替换它。 现在我们已经掌握了所需要的理论知识。

下面这个程序是我们将要hack的程序,正常情况下它循环输出字符串Holberton

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

/**              
 * main - uses strdup to create a new string, loops forever-ever
 *                
 * Return: EXIT_FAILURE if malloc failed. Other never returns
 */
int main(void)
{
     char *s;
     unsigned long int i;

     s = strdup("Holberton");
     if (s == NULL)
     {
          fprintf(stderr, "Can't allocate mem with malloc\n");
          return (EXIT_FAILURE);
     }
     i = 0;
     while (s)
     {
          printf("[%lu] %s (%p)\n", i, s, (void *)s);
          sleep(1);
          i++;
     }
     return (EXIT_SUCCESS);
}

编译运行该程序,它将循环输出字符串Holberton直到进程被杀死。

julien@holberton:~/holberton/w/hackthevm0$ gcc -Wall -Wextra -pedantic -Werror loop.c -o loop
julien@holberton:~/holberton/w/hackthevm0$ ./loop 
[0] Holberton (0xfbd010)
[1] Holberton (0xfbd010)
[2] Holberton (0xfbd010)
[3] Holberton (0xfbd010)
[4] Holberton (0xfbd010)
[5] Holberton (0xfbd010)
[6] Holberton (0xfbd010)
[7] Holberton (0xfbd010)
...

感兴趣的话,你可以暂停阅读本文,尝试写个脚本/程序寻找进程堆中的字符串。

 

/proc/pid/maps

如之前所见,文件/proc/pid/maps是个文本文件,我们可以直接读取,内容如下:

julien@ubuntu:/proc/4618$ cat maps
00400000-00401000 r-xp 00000000 08:01 1070052                            /home/julien/holberton/w/funwthevm/loop
00600000-00601000 r--p 00000000 08:01 1070052                            /home/julien/holberton/w/funwthevm/loop
00601000-00602000 rw-p 00001000 08:01 1070052                            /home/julien/holberton/w/funwthevm/loop
010ff000-01120000 rw-p 00000000 00:00 0                                  [heap]
7f144c052000-7f144c20c000 r-xp 00000000 08:01 136253                     /lib/x86_64-linux-gnu/libc-2.19.so
7f144c20c000-7f144c40c000 ---p 001ba000 08:01 136253                     /lib/x86_64-linux-gnu/libc-2.19.so
7f144c40c000-7f144c410000 r--p 001ba000 08:01 136253                     /lib/x86_64-linux-gnu/libc-2.19.so
7f144c410000-7f144c412000 rw-p 001be000 08:01 136253                     /lib/x86_64-linux-gnu/libc-2.19.so
7f144c412000-7f144c417000 rw-p 00000000 00:00 0 
7f144c417000-7f144c43a000 r-xp 00000000 08:01 136229                     /lib/x86_64-linux-gnu/ld-2.19.so
7f144c61e000-7f144c621000 rw-p 00000000 00:00 0 
7f144c636000-7f144c639000 rw-p 00000000 00:00 0 
7f144c639000-7f144c63a000 r--p 00022000 08:01 136229                     /lib/x86_64-linux-gnu/ld-2.19.so
7f144c63a000-7f144c63b000 rw-p 00023000 08:01 136229                     /lib/x86_64-linux-gnu/ld-2.19.so
7f144c63b000-7f144c63c000 rw-p 00000000 00:00 0 
7ffc94272000-7ffc94293000 rw-p 00000000 00:00 0                          [stack]
7ffc9435e000-7ffc94360000 r--p 00000000 00:00 0                          [vvar]
7ffc94360000-7ffc94362000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

回想前面的内容,可以看到,栈([stack])位于内存的高地址,堆([heap])位于内存的低地址。

[heap]

maps文件中, 我们可以找到搜索字符串需要的所有信息:

010ff000-01120000 rw-p 00000000 00:00 0                                  [heap]

这个进程的堆信息如下:

  • 在虚拟内存中的起始地址是0x010ff000
  • 结束地址是01120000
  • 权限是可读写的(rw

回顾下正在运行的loop的输出:

...
[1024] Holberton (0x10ff010)
...

0x010ff000 < 0x10ff010 < 0x01120000。这证明了我们的字符串是在堆上。更精确的说,新字符串是在堆偏移0x10的地方。

如果我们打开文件/proc/4618/mem并且将文件指针移动到0x10ff010, 我们就能替换正在运行的程序loop中的字符串Holberton

我们接下来会写个程序/脚本做这件事情。
你也可以暂停阅读本文,用自己最熟悉的语言尝试写个脚本/程序来做这件事情。

.

.

.

替换虚拟内存中的字符串###

下面是我们用Python3 实现字符串替换的脚本(read_write_heap.py):

#!/usr/bin/env python3
'''             
Locates and replaces the first occurrence of a string in the heap
of a process    

Usage: ./read_write_heap.py PID search_string replace_by_string
Where:           
- PID is the pid of the target process
- search_string is the ASCII string you are looking to overwrite
- replace_by_string is the ASCII string you want to replace
  search_string with
'''

import sys

def print_usage_and_exit():
    print('Usage: {} pid search write'.format(sys.argv[0]))
    sys.exit(1)

# check usage  
if len(sys.argv) != 4:
    print_usage_and_exit()

# get the pid from args
pid = int(sys.argv[1])
if pid <= 0:
    print_usage_and_exit()
search_string = str(sys.argv[2])
if search_string  == "":
    print_usage_and_exit()
write_string = str(sys.argv[3])
if search_string  == "":
    print_usage_and_exit()

# open the maps and mem files of the process
maps_filename = "/proc/{}/maps".format(pid)
print("[*] maps: {}".format(maps_filename))
mem_filename = "/proc/{}/mem".format(pid)
print("[*] mem: {}".format(mem_filename))

# try opening the maps file
try:
    maps_file = open('/proc/{}/maps'.format(pid), 'r')
except IOError as e:
    print("[ERROR] Can not open file {}:".format(maps_filename))
    print("        I/O error({}): {}".format(e.errno, e.strerror))
    sys.exit(1)

for line in maps_file:
    sline = line.split(' ')
    # check if we found the heap
    if sline[-1][:-1] != "[heap]":
        continue
    print("[*] Found [heap]:")

    # parse line
    addr = sline[0]
    perm = sline[1]
    offset = sline[2]
    device = sline[3]
    inode = sline[4]
    pathname = sline[-1][:-1]
    print("\tpathname = {}".format(pathname))
    print("\taddresses = {}".format(addr))
    print("\tpermisions = {}".format(perm))
    print("\toffset = {}".format(offset))
    print("\tinode = {}".format(inode))

    # check if there is read and write permission
    if perm[0] != 'r' or perm[1] != 'w':
        print("[*] {} does not have read/write permission".format(pathname))
        maps_file.close()
        exit(0)

    # get start and end of the heap in the virtual memory
    addr = addr.split("-")
    if len(addr) != 2: # never trust anyone, not even your OS :)
        print("[*] Wrong addr format")
        maps_file.close()
        exit(1)
    addr_start = int(addr[0], 16)
    addr_end = int(addr[1], 16)
    print("\tAddr start [{:x}] | end [{:x}]".format(addr_start, addr_end))

    # open and read mem
    try:
        mem_file = open(mem_filename, 'rb+')
    except IOError as e:
        print("[ERROR] Can not open file {}:".format(mem_filename))
        print("        I/O error({}): {}".format(e.errno, e.strerror))
        maps_file.close()
        exit(1)

    # read heap  
    mem_file.seek(addr_start)
    heap = mem_file.read(addr_end - addr_start)

    # find string
    try:
        i = heap.index(bytes(search_string, "ASCII"))
    except Exception:
        print("Can't find '{}'".format(search_string))
        maps_file.close()
        mem_file.close()
        exit(0)
    print("[*] Found '{}' at {:x}".format(search_string, i))

    # write the new string
    print("[*] Writing '{}' at {:x}".format(write_string, addr_start + i))
    mem_file.seek(addr_start + i)
    mem_file.write(bytes(write_string, "ASCII"))

    # close files
    maps_file.close()
    mem_file.close()

    # there is only one heap in our example
    break

注意:需要以root权限执行上面的脚本, 否则无法读写文件/proc/pid/mem, 即使你是进程的所有者。

运行上面的脚本:

julien@holberton:~/holberton/w/hackthevm0$ sudo ./read_write_heap.py 4618 Holberton "Fun w vm!"
[*] maps: /proc/4618/maps
[*] mem: /proc/4618/mem
[*] Found [heap]:
    pathname = [heap]
    addresses = 010ff000-01120000
    permisions = rw-p
    offset = 00000000
    inode = 0
    Addr start [10ff000] | end [1120000]
[*] Found 'Holberton' at 10
[*] Writing 'Fun w vm!' at 10ff010
julien@holberton:~/holberton/w/hackthevm0$ 

可以看到上面脚本打印出来的地址和我们手动找到的是一致的:

  • 进程的堆位于虚拟内存的0x010ff000 ~ 0x01120000
  • 我们要找的字符串地址是0x10ff010, 相对于堆的起始地址偏移了0x10

回过头来看下我们的loop程序,它应该会打印字符串”fun w vm!”

...
[2676] Holberton (0x10ff010)
[2677] Holberton (0x10ff010)
[2678] Holberton (0x10ff010)
[2679] Holberton (0x10ff010)
[2680] Holberton (0x10ff010)
[2681] Holberton (0x10ff010)
[2682] Fun w vm! (0x10ff010)
[2683] Fun w vm! (0x10ff010)
[2684] Fun w vm! (0x10ff010)
[2685] Fun w vm! (0x10ff010)

六、下节预告

下一篇文章中我们要做的事情和本章类似, 不同的是我们将访问并修改一个Python3 脚本的内存。 这做起来比较吃力, 所以我们需要了解Pyhton3 内部的一些机制。不信你可以试试,上面的脚本read_write_heap.py并不能修改Python3进程中的ASCII字符串。

七、继续阅读

八、原文链接

Hack The Virtual Memory: C strings & /proc


Similar Posts

 

 


 

Proc/pid/maps显示进程映射了的内存区域和访问权限。对应内核中的操作集为proc_pid_maps_op,具体的导出函数为show_map。

内核中进程的一段地址空间用一个vm_area_struct结构体表示,所有地址空间存储在task->mm->mmap链表中。

一个文件可以映射到进程的一段内存区域中,映射的文件描述符保存在vm_area_struct->vm_file域中,这种内存区域叫做有名内存区域,相反,属于匿名映射内存区域。

Vm_area_struct每项对应解析如下表所示:

 

 

 

下面一起看下一个proc maps的例子。

cat /proc/19970/task/19970/maps

001f7000-00212000 r-xp 00000000 fd:00 2719760 /lib/ld-2.5.so

00212000-00213000 r-xp 0001a000 fd:00 2719760 /lib/ld-2.5.so

00213000-00214000 rwxp 0001b000 fd:00 2719760 /lib/ld-2.5.so

00214000-0036b000 r-xp 00000000 fd:00 2719767 /lib/libc-2.5.so

0036b000-0036d000 r-xp 00157000 fd:00 2719767 /lib/libc-2.5.so

0036d000-0036e000 rwxp 00159000 fd:00 2719767 /lib/libc-2.5.so

0036e000-00371000 rwxp 0036e000 00:00 0

0054f000-00565000 r-xp 00000000 fd:00 2719791 /lib/libpthread-2.5.so

00565000-00566000 r-xp 00015000 fd:00 2719791 /lib/libpthread-2.5.so

00566000-00567000 rwxp 00016000 fd:00 2719791 /lib/libpthread-2.5.so

00567000-00569000 rwxp 00567000 00:00 0

006f5000-006f6000 r-xp 006f5000 00:00 0 [vdso]

08048000-08049000 r-xp 00000000 fd:00 3145810 /home/lijz/code/pthread

08049000-0804a000 rw-p 00000000 fd:00 3145810 /home/lijz/code/pthread

08c50000-08c71000 rw-p 08c50000 00:00 0 [heap]

b75d7000-b75d8000 ---p b75d7000 00:00 0

b75d8000-b7fda000 rw-p b75d8000 00:00 0

b7fe4000-b7fe5000 rw-p b7fe4000 00:00 0

bf987000-bf99c000 rw-p bffea000 00:00 0 [stack]

进程的每段地址空间由struct vm_area_struct 描述。如上所示的每一行对应一个vm_area_struct结构体

一个文件可以映射到内存中,vm_area_struct的vm_file保存了文件描述符,这种映射称为有名映射,反之则为匿名映射

下面以第十四行为例,解释各例的内容。

第一列:08049000-0804a000-----本段内存映射的虚拟地址空间范围,对应vm_area_struct中的vm_start和vm_end。

第二列:rw-p----权限 r-读,w-写 x-可执行 p-私有,对应vm_flags。

第三列:00000000----针对有名映射,指本段映射地址在文件中的偏移,对应vm_pgoff。 对匿名映射而言,为vm_area_struct->vm_start。

第四列:fd:00----所映射的文件所属设备的设备号,对应vm_file->f_dentry->d_inode->i_sb->s_dev。匿名映射为0。其中fd为主设备号,00为次设备号。

第五列:3145810----文件的索引节点号,对应vm_file->f_dentry->d_inode->i_ino,与ls –i显示的内容相符。匿名映射为0。

第六列:/home/lijz/code/pthread---所映射的文件名。对有名映射而言,是映射的文件名,对匿名映射来说,是此段内存在进程中的作用。

[stack]表示本段内存作为栈来使用,[heap]作为堆来使用,其他情况则为无。

 

经过上面的分析,proc maps中的每一列代表的意思已经非常清晰了,接下来看下proc每maps中每一行的解析。

各共享库的代码段,存放着二进制可执行的机器指令,由kernel把该库ELF文件的代码段map到虚存空间;

各共享库的数据段,存放着程序执行所需的全局变量,由kernel把ELF文件的数据段map到虚存空间;

用户代码段,存放着二进制形式的可执行的机器指令,由kernel把ELF文件的代码段map到虚存空间;

用户数据段,存放着程序执行所需的全局变量,由kernel把ELF文件的数据段map到虚存空间;

堆(heap),当且仅当malloc调用时存在,由kernel把匿名内存map到虚存空间,堆则在程序中没有调用malloc的情况下不存在;

栈(stack),作为进程的临时数据区,由kernel把匿名内存map到虚存空间,栈空间的增长方向是从高地址到低地址。

pthread这个应用程序在maps中占用了两行,内容如下:

08048000-08049000 r-xp 00000000 fd:00 3145810 /home/lijz/code/pthread

08049000-0804a000 rw-p 00000000 fd:00 3145810 /home/lijz/code/pthread

其中第一行的权限是只读,并且可执行,说明第一行是应用程序的代码段,而第二行的权限是可读可写,但是没有执行权限,说明该段是pthread的数据段

 

00c56000-00dad000 r-xp 00000000 fd:00 2719767 /lib/libc-2.5.so

00dad000-00daf000 r-xp 00157000 fd:00 2719767 /lib/libc-2.5.so

00daf000-00db0000 rwxp 00159000 fd:00 2719767 /lib/libc-2.5.so

以上是libc-2.5共享库在maps文件中的记录,每个共享库在maps文件中对应着三行,分别是数据段与代码段。

 

堆[heap]段。

08c64000-08c85000 rw-p 08c64000 00:00 0 [heap]

有些maps文件并不会出现该记录,这主要跟程序中有无使用malloc相关,如果主线程使用了malloc就会有该记录,否则就没有。

子线程中调用malloc,会产生另外的堆映射,但是并不会标记[heap]

例如,在子线程中动态分配1MB的内存空间,pthread2应用程序的执行结果如下所示:

tid addr 0xbfd818f0

child thread run

stackbase 0xb7f4f3c0

stackaddr =0x7754e008----malloc分配的地址

guardsize 4096

对应的maps文件:

08048000-08049000 r-xp 00000000 fd:00 3145811 /home/lijz/code/pthread2

08049000-0804a000 rw-p 00000000 fd:00 3145811 /home/lijz/code/pthread2

0945a000-0947b000 rw-p 0945a000 00:00 0 [heap]

7754e000-b754f000 rw-p 7754e000 00:00 0 -----------区间大小正是1MB

b754f000-b7550000 ---p b754f000 00:00 0

b7550000-b7f52000 rw-p b7550000 00:00 0

b7f5c000-b7f5d000 rw-p b7f5c000 00:00 0

bfd6e000-bfd83000 rw-p bffea000 00:00 0 [stack]

maps文件中红色标注的行,从内容上看,本段内存大小是1MB,权限为读写私有,偏移为本段内存的开始地址,设备号和文件索引节点为0。

可以看出本段内存是进程通过mmap映射的一段空间,是匿名映射。

在pthread2程序中,正好用malloc分配了一个1MB的内存,能够与这段内存对应。

同时,malloc分配的地址0x7754e008正落在该区间,并且偏向区间低地址部分,说明该区间是个堆地址空间。说明了这段1M的内存确实是进程调用malloc分配的,其中malloc又调用mmap系统调用匿名映射。

 

栈段[stack],下面用几个例子来说明栈段。

bfd50000-bfd65000 rw-p bffea000 00:00 0 [stack]

对于单线程应用程序而言,只有一个[stack]段,对应多线程应用程序,[stack]段是主线程的栈空间,子线程的栈空间则用pthread库自动分配

例1,将一个单线程的应用的局部变量的地址打印出来,执行的结果如下所示:

./pthread2

tid addr 0xbfc73600

对应的maps文件:

08048000-08049000 r-xp 00000000 fd:00 3145811 /home/lijz/code/pthread2

08049000-0804a000 rw-p 00000000 fd:00 3145811 /home/lijz/code/pthread2

b7f7e000-b7f80000 rw-p b7f7e000 00:00 0

b7f8a000-b7f8b000 rw-p b7f8a000 00:00 0

bfc5f000-bfc74000 rw-p bffea000 00:00 0 [stack]

局部变量的地址0xbfc73600在[stack]区间。

 

例2:将一个拥有一个子线程的应用局部变量打印出来,执行的结果如下所示:

tid addr 0xbfd64740---------主线程中打印的局部变量地址

child thread run

stackaddr 0xb7fc93c4--------子线程中打印的局部变量地址

guardsize 4096---------栈保护页大小

对应的maps文件如下:

08048000-08049000 r-xp 00000000 fd:00 3145811 /home/lijz/code/pthread2

08049000-0804a000 rw-p 00000000 fd:00 3145811 /home/lijz/code/pthread2

08c64000-08c85000 rw-p 08c64000 00:00 0 [heap]

b75c9000-b75ca000 ---p b75c9000 00:00 0---------pthread_create默认的栈溢出保护区

b75ca000-b7fcc000 rw-p b75ca000 00:000------------pthread_create创建的子线程的栈空间

b7fd6000-b7fd7000 rw-p b7fd6000 00:00 0------------------4KB应该也是通过mmap产生的匿名映射

bfd50000-bfd65000 rw-p bffea000 00:00 0 [stack]---------主进程的栈空间

 

由上执行结果显示,主线程中局部变量地址0xbfd64740落在[stack]区间,

而子线程局部变量地址0xb7fc93c4则落在b75ca000-b7fcc000 rw-p b75ca00区间,并且局部变量的地址从高地址开始分配,说明该VMA正是子线程的栈地址空间。

另外,对栈空间,pthread默认设置了一个4KB的栈保护页,对应的区间为:b75c9000-b75ca000---p b75c9000,该区间不可读,不可写,也不能执行,通过这些属性信息的设置,可以达到栈溢出保护的作用。

 

 

例3:在例2的基础上,多创建一个线程,pthread2程序的执行结果如下所示:

./pthread2

tid addr 0xbfc81610----------主线程局部变量地址

child thread run

stackaddr = 0xb7f183c0-------子线程1局部变量地址

guardsize 4096

child thread2 run

stackaddr =0xb75173c4 ----------子线程局部变量地址

guardsize 4096

对应的maps文件:

08048000-08049000 r-xp 00000000 fd:00 3145811 /home/lijz/code/pthread2

08049000-0804a000 rw-p 00000000 fd:00 3145811 /home/lijz/code/pthread2

092d6000-092f7000 rw-p 092d6000 00:00 0 [heap]

76b16000-b6b17000 rw-p 76b16000 00:00 0 ----------mallocmmap

b6b17000-b6b18000 ---p b6b17000 00:00 0

b6b18000-b7518000 rw-p b6b18000 00:000---------pthread thread2 stack space

b7518000-b7519000 ---p b7518000 00:00 0

b7519000-b7f1b000 rw-p b7519000 00:000----------pthread thread1 stack space

b7f25000-b7f26000 rw-p b7f25000 00:00 0

bfc6e000-bfc83000 rw-p bffea000 00:00 0 [stack]---main thread stack space

 

从maps文件记录上看,增加一个子线程,在maps文件中就增加了两条记录,分别是子线程的栈空间和栈保护页的记录

默认情况下,pthread为子线程预留的栈空间大小为1MB,栈保护页为4KB(这主要跟页大小相关)。

总之,proc maps文件可以查看进程的内存映射,每一段内存的权限属性等信息。