为什么Linux程序的.text部分从0x0804800开始,堆栈顶部从0xbffffff开始?

5
根据黑客的汇编入门(第二部分)虚拟内存组织,Linux程序.text段从0x0804800开始,堆栈顶部从0xbffffff开始。这些数字的意义是什么?为什么不从0x0000000(或0x00000200x0000040以跳过NULL的下一个32位或64位)开始.text?为什么不将堆栈顶部设置为0xfffffff
2个回答

8
让我们先说一下:大多数情况下,各个部分不需要放置在特定位置,更重要的是布局。现在,堆栈顶实际上是随机的,请参见此处
在Linux/x86上,0x08048000是ld在第一个PT_LOAD段上启动的默认地址。在Linux/amd64上,默认值为0x400000,您可以使用自定义链接器脚本更改默认值。您还可以使用-Wl,-Ttext,0xNNNNNNNN标志将.text节的起始位置更改为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(内核映射的动态代码库,提供一些接口给内核使用)。需要注意的是堆的起始地址也是随机化的。

3

这里并没有太多重要的内容。

堆栈通常向下增长(较低地址),因此将其放置在高地址上并留有一些空间以便向较低地址扩展是合理的(但不是必须的)。

至于为什么不使用地址0来存储程序段,这里有一些逻辑。首先,很多软件使用0作为C和C ++中合法的无效指针NULL,不应被引用。很多软件存在bug,实际上会尝试在地址0处读取或写入内存,而没有进行适当的指针验证。如果使地址0周围的内存区域对程序不可访问,就可以发现其中的一些bug(程序将在调试器中崩溃或停止)。另外,由于NULL是一个合法的无效指针,所以该地址处不应有数据或代码(如果有,则无法将指向它的指针与NULL区分开)。

在x86平台上,通过虚拟到物理地址转换通常使地址0周围的内存不可访问。页面表以这样的方式设置,即虚拟地址0的条目不由物理内存页面支持,并且页面通常为4 KB大小,而不仅仅是一些字节。这就是为什么如果去掉地址0,则还会连带去掉地址1到4095的原因。在地址0处占用超过4 KB的地址空间也是合理的。原因是C和C ++中结构体的指针。您可以将一个结构体的NULL指针与试图访问的结构成员之间的距离相加,以获得尝试访问内存的地址(0),该距离为您要访问的成员与结构开头的距离(第一个成员为0,其余成员大于0)。

可能还有其他选择特定地址范围的考虑因素,但我无法代表所有情况发言。操作系统可能希望将一些与程序相关的内容(数据结构)保留在程序本身内部,因此为什么不在可访问地址空间的一端附近使用固定位置呢?


1
这都是有道理的,但这比NULL(0x0004096 vs. 0x804800)高了200个数量级。您还没有提到堆栈不从内存字面顶部(0xfffffff)开始的原因。 - Heath Borders
我不知道为什么.text从那么高的位置开始。但是为什么不行呢?我已经在最后一段中解决了栈位置的问题。 - Alexey Frunze
1
通常,32位Linux内核使用从0xC0000000开始的地址。一般来说,在用户模式下尝试访问此地址范围会导致段错误。 - Dipstick
通常是这样。但请注意,问题中的所有十六进制数都奇怪地为28位(7个十六进制数字)。 - Alexey Frunze

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接