理论
我们假设在32位Linux下进行编程,首先要明确,我们的虚拟地址空间有4G,4G的地址空间的寻址范围就是 0x0000_0000 ~ 0xFFFF_FFFF
,在Linux下,高地址的1G内存是给操作系统使用的,也就是 0xC000_0000 ~ 0xFFFF_FFFF
,这一段地址我们无法使用,可以使用的只有 0x0000_0000~0xBFFF_FFFF
这一段地址。
其次要知道程序会分为那些段
- .text 代码段:可执行程序代码(只读)
- .rodata 只读数据段: 程序中的常量(只读)
- .data 初始化数据段: 程序中已经初始化的全局变量。(可读可写)
- .bss 未初始数据段: 程序中未初始的全局变量。(可读可写)
- heap 堆区:程序中动态分配的内存。堆的内存向上增长。(可读可写)
- 共享数据区:
- stack 栈区:程序中的函数、函数的形参、函数的局部变量 。(可读可写)
我们的程序,编译为汇编文件的过程中,会有很多的节(section),链接器将这些目标文件中属性相同的节(section)合并成段(segment),因此一个段是由多个节组成的,我们平时所说的C程序内存空间中的数据段、代码段就是指合并后的segment。
为什么要将section合并成segment?这么做的原因也很简单,一是为了保护模式下的安全检查,二是为了操作系统在加载程序时省事。在保护模式下对内存的访问必须要经过段描述符,段描述符用来描述一段内存区域的访问属性,其中的S位和TYPE位可组合成多种权限属性,处理器用这些属性来限制程序对内存的使用,如果程序对某片内存的访问方式不符合该内存所对应的段描述符(由访问内存时所使用的选择子决定)中设置的权限,比如对代码这种具备只读属性的内存区域执行了写操作,处理器会检查到这种情况并抛出GP异常。
扯远了,那么我们的内存布局就可以看下面这个图:
其中多了一个区域 dynamic library
动态装载区,这个区域用于映射装载的动态链接库。在Linux下,如果可执行文件依赖其他共享库,那么系统就会为它在从0x40000000
开始的地址分配相应的空间,并将共享库载入到该空间。
其中我们发现,蓝色的是只读的,橘色的是可读可写的数据,红色的是堆栈。其实其存放是按照权限来的,这也符合我们上面将的和操作系统对权限的检查相吻合。
实例
只讲理论是很难理清的,我们举个例子
我们写个c的程序
#include <stdlib.h>
int main(){
int i = 0;
while (1) {
sleep(1000);
}
return 0;
}
编译一下
gcc temp.c -o temp.out -fno-pic -m32
其中 -no-pie
是不将程序地址随机化,-m32
是将程序编译为32位程序,因为现在使用的都是64位系统,我们这里只讲32位,实际上本质是一样的。
执行这个程序
./temp.out
找到这个程序的pid
ps -C temp.out
得到程序的PID
➜ ~ ps -C temp.out
PID TTY TIME CMD
33862 pts/2 00:00:00 temp.out
根据PID获取程序的内存映射
pmap -d 33862
得到内存映射关系
➜ ~ pmap -d 34204
34204: ./temp.out
Address Kbytes Mode Offset Device Mapping
0000000008048000 4 r---- 0000000000000000 008:00005 temp.out
0000000008049000 4 r-x-- 0000000000001000 008:00005 temp.out
000000000804a000 4 r---- 0000000000002000 008:00005 temp.out
000000000804b000 4 r---- 0000000000002000 008:00005 temp.out
000000000804c000 4 rw--- 0000000000003000 008:00005 temp.out
00000000f7d25000 100 r---- 0000000000000000 008:00005 libc-2.31.so
00000000f7d3e000 1388 r-x-- 0000000000019000 008:00005 libc-2.31.so
00000000f7e99000 464 r---- 0000000000174000 008:00005 libc-2.31.so
00000000f7f0d000 4 ----- 00000000001e8000 008:00005 libc-2.31.so
00000000f7f0e000 8 r---- 00000000001e8000 008:00005 libc-2.31.so
00000000f7f10000 4 rw--- 00000000001ea000 008:00005 libc-2.31.so
00000000f7f11000 12 rw--- 0000000000000000 000:00000 [ anon ]
00000000f7f28000 8 rw--- 0000000000000000 000:00000 [ anon ]
00000000f7f2a000 16 r---- 0000000000000000 000:00000 [ anon ]
00000000f7f2e000 8 r-x-- 0000000000000000 000:00000 [ anon ]
00000000f7f30000 4 r---- 0000000000000000 008:00005 ld-2.31.so
00000000f7f31000 120 r-x-- 0000000000001000 008:00005 ld-2.31.so
00000000f7f4f000 44 r---- 000000000001f000 008:00005 ld-2.31.so
00000000f7f5b000 4 r---- 000000000002a000 008:00005 ld-2.31.so
00000000f7f5c000 4 rw--- 000000000002b000 008:00005 ld-2.31.so
00000000fff9c000 132 rw--- 0000000000000000 000:00000 [ stack ]
mapped: 2340K writeable/private: 164K shared: 0K
当然我们也可以换一个命令
➜ ~ cat /proc/33862/maps
08048000-08049000 r--p 00000000 08:05 658197 /home/lovetzp/Desktop/temp.out
08049000-0804a000 r-xp 00001000 08:05 658197 /home/lovetzp/Desktop/temp.out
0804a000-0804b000 r--p 00002000 08:05 658197 /home/lovetzp/Desktop/temp.out
0804b000-0804c000 r--p 00002000 08:05 658197 /home/lovetzp/Desktop/temp.out
0804c000-0804d000 rw-p 00003000 08:05 658197 /home/lovetzp/Desktop/temp.out
f7d25000-f7d3e000 r--p 00000000 08:05 569025 /usr/lib/i386-linux-gnu/libc-2.31.so
f7d3e000-f7e99000 r-xp 00019000 08:05 569025 /usr/lib/i386-linux-gnu/libc-2.31.so
f7e99000-f7f0d000 r--p 00174000 08:05 569025 /usr/lib/i386-linux-gnu/libc-2.31.so
f7f0d000-f7f0e000 ---p 001e8000 08:05 569025 /usr/lib/i386-linux-gnu/libc-2.31.so
f7f0e000-f7f10000 r--p 001e8000 08:05 569025 /usr/lib/i386-linux-gnu/libc-2.31.so
f7f10000-f7f11000 rw-p 001ea000 08:05 569025 /usr/lib/i386-linux-gnu/libc-2.31.so
f7f11000-f7f14000 rw-p 00000000 00:00 0
f7f28000-f7f2a000 rw-p 00000000 00:00 0
f7f2a000-f7f2e000 r--p 00000000 00:00 0 [vvar]
f7f2e000-f7f30000 r-xp 00000000 00:00 0 [vdso]
f7f30000-f7f31000 r--p 00000000 08:05 569021 /usr/lib/i386-linux-gnu/ld-2.31.so
f7f31000-f7f4f000 r-xp 00001000 08:05 569021 /usr/lib/i386-linux-gnu/ld-2.31.so
f7f4f000-f7f5a000 r--p 0001f000 08:05 569021 /usr/lib/i386-linux-gnu/ld-2.31.so
f7f5b000-f7f5c000 r--p 0002a000 08:05 569021 /usr/lib/i386-linux-gnu/ld-2.31.so
f7f5c000-f7f5d000 rw-p 0002b000 08:05 569021 /usr/lib/i386-linux-gnu/ld-2.31.so
fff9c000-fffbd000 rw-p 00000000 00:00 0 [stack]
也可以得到内存映射关系。
从内存的分布中我们可以找到栈区,以及动态链接的一些库,以及头部的五个来自temp.out的区,这个内存映射分布与我们图并不是那么一致,是因为操作系统在处理segment段的时候,有自己的一些理解在里面,具体的就得看操作系统的实现了。
而且我们没有发现有堆区,这个我们可以在程序中加一个malloc,再看一下内存布局就有了。
库,以及头部的五个来自temp.out的区,这个内存映射分布与我们图并不是那么一致,是因为操作系统在处理segment段的时候,有自己的一些理解在里面,具体的就得看操作系统的实现了。
而且我们没有发现有堆区,这个我们可以在程序中加一个malloc,再看一下内存布局就有了。