/proc/pid/maps 文件信息分析

Proc/pid/maps显示进程映射了的内存区域和访问权限。对应内核中的操作集为proc_pid_maps_op,具体的导出函数为show_map。内核中进程的一段地址空间用一个vm_area_struct结构体表示,所有地址空间存储在task->mm->mmap链表中。

一个文件可以映射到进程的一段内存区域中,映射的文件描述符保存在vm_area_struct->vm_file域中,这种内存区域叫做有名内存区域,相反,属于匿名映射内存区域。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 映射文件所属节点号。对匿名映射来说,因为没有文件在磁盘上,所以没有节点号,始终为00:00。对有名映射来说,是映射的文件的节点号
第七列,如/lib/ld-2.5.so 对有名来说,是映射的文件名。对匿名映射来说,是此段虚拟内存在进程中的角色。[stack]表示在进程中作为栈使用,[heap]表示堆。其余情况则无显示

下面一起看下一个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
00054f000-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 
0006f5000-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应用程序的执行结果如下所示:

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

对应的maps文件:

08048000-08049000 r-xp 00000000 fd:00 3145811    /home/lijz/code/pthread
208049000-0804a000 rw-p 00000000 fd:00 3145811    /home/lijz/code/pthread
20945a000-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/pthread
208049000-0804a000 rw-p 00000000 fd:00 3145811    /home/lijz/code/pthread
2b7f7e000-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/pthread
208049000-0804a000 rw-p 00000000 fd:00 3145811    /home/lijz/code/pthread
208c64000-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/pthread
208049000-0804a000 rw-p 00000000 fd:00 3145811    /home/lijz/code/pthread
2092d6000-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文件可以查看进程的内存映射,每一段内存的权限属性等信息。

posted @ 2021-02-02 17:06  Max_hhg  阅读(557)  评论(0编辑  收藏  举报