根据Assembly Primer For Hackers (Part 2) Virtual Memory Organization,Linux 程序.text
部分从 开始0x0804800
,堆栈顶部从 开始0xbffffff
。这些数字有什么意义?为什么不从.text
(0x0000000
或0x0000020
过去0x0000040
32 或 64 位NULL
)开始?为什么不从堆栈顶部开始0xfffffff
?
2 回答
让我们先这么说吧:大多数时候,各个部分不需要放在特定的位置,更重要的是布局。如今,堆栈顶部实际上是随机的,请参见此处。
0x08048000 是 ldPT_LOAD
在 Linux/x86 上启动第一个段的默认地址。在 Linux/amd64 上,默认值为 0x400000,您可以使用自定义链接描述文件更改默认值。您还可以将 .text 部分以-Wl,-Ttext,0xNNNNNNNN
标志开头的位置更改为 gcc。要了解为什么 .text 没有映射到地址 0,请记住,为方便起见,NULL 指针通常映射到 ((void *) 0)。因此,将零页映射为不可访问以捕获 NULL 指针的使用是有用的。.text 开头之前的内存其实被很多东西占用了;举cat /proc/self/maps
个例子:
$ cat /proc/self/maps
001c0000-00317000 r-xp 00000000 08:01 245836 /lib/libc-2.12.1.so
00317000-00318000 ---p 00157000 08:01 245836 /lib/libc-2.12.1.so
00318000-0031a000 r--p 00157000 08:01 245836 /lib/libc-2.12.1.so
0031a000-0031b000 rw-p 00159000 08:01 245836 /lib/libc-2.12.1.so
0031b000-0031e000 rw-p 00000000 00:00 0
00376000-00377000 r-xp 00000000 00:00 0 [vdso]
00852000-0086e000 r-xp 00000000 08:01 245783 /lib/ld-2.12.1.so
0086e000-0086f000 r--p 0001b000 08:01 245783 /lib/ld-2.12.1.so
0086f000-00870000 rw-p 0001c000 08:01 245783 /lib/ld-2.12.1.so
08048000-08051000 r-xp 00000000 08:01 2244617 /bin/cat
08051000-08052000 r--p 00008000 08:01 2244617 /bin/cat
08052000-08053000 rw-p 00009000 08:01 2244617 /bin/cat
09ab5000-09ad6000 rw-p 00000000 00:00 0 [heap]
b7502000-b7702000 r--p 00000000 08:01 4456455 /usr/lib/locale/locale-archive
b7702000-b7703000 rw-p 00000000 00:00 0
b771b000-b771c000 r--p 002a1000 08:01 4456455 /usr/lib/locale/locale-archive
b771c000-b771e000 rw-p 00000000 00:00 0
bfbd9000-bfbfa000 rw-p 00000000 00:00 0 [stack]
我们在这里看到的是 C 库、动态加载器 ld.so 和内核 VDSO(内核映射的动态代码库,为内核提供了一些接口)。请注意,堆的开始也是随机的。
没有太大意义。
堆栈通常会向下增长(到低地址),因此将其放置在高地址并有一些空间向低地址扩展是有点合理的(但不是强制性的)。
至于程序段不使用地址0,这里有一些逻辑。首先,很多软件使用 0 表示NULL
C 和 C++ 中的合法无效指针,不应取消引用。许多软件都有错误,因为它实际上试图在地址 0 处读取或写入内存而没有适当的指针验证。如果您使程序无法访问地址 0 周围的内存区域,您可以发现其中的一些错误(程序将在调试器中崩溃或停止)。此外,由于NULL
是合法的无效指针,因此该地址不应有数据或代码(如果有,您无法区分指向它的指针NULL
)。
在 x86 平台上,地址 0 周围的内存通常通过虚拟地址到物理地址的转换而变得不可访问。页表的设置方式是,虚拟地址 0 的条目不由物理内存页备份,并且页的大小通常为 4 KB,而不仅仅是几个字节。这就是为什么如果你取出地址 0,你也会取出地址 1 到 4095。在地址 0 处取出超过 4 KB 的地址空间也是合理的。原因是指向 C 和 C++ 中的结构的指针。你可以有一个NULL
指向结构的指针,当您取消引用它时,尝试的内存访问发生在指针 (0) 中包含的地址加上您尝试访问的结构成员与结构的开头之间的距离(第一个成员为 0 , 其余大于 0)。
为程序选择特定地址范围可能还有其他一些考虑因素,但我不能代表所有这些因素。操作系统可能希望在程序本身中保留一些与程序相关的东西(数据结构),那么为什么不使用一个固定位置来靠近地址空间可访问部分的一端呢?