为什么ELF执行入口点虚拟地址为0x80xxxxx而不是0x0?

Mic*_* L. 19 point elf virtual-address-space

执行时,程序将从虚拟地址0x80482c0开始运行.此地址不指向我们的main()过程,而是指向_start由链接器创建的名为的过程.

到目前为止,我的谷歌研究只是让我得到了一些(含糊的)历史猜测:

有民间传说,0x08048000曾经是由加利福尼亚州圣克鲁斯市的一个团体颁布的*NIX到i386的端口上的STACK_TOP(也就是说,堆栈从接近0x08048000下降到0).这是因为128MB的RAM很昂贵,4GB的RAM是不可想象的.

任何人都可以确认/否认这个吗?

Tho*_*nin 34

正如Mads指出的那样,为了通过空指针捕获大多数访问,类Unix系统倾向于使地址为零的页面"未映射".因此,访问会立即触发CPU异常,换句话说就是段错误.这比让应用程序变得流氓要好得多.但是,异常向量表可以位于任何地址,至少在x86处理器上(有一个特殊的寄存器,加载了lidt操作码).

起点地址是描述内存布局方式的一组约定的一部分.链接器在生成可执行二进制文件时必须知道这些约定,因此它们不太可能更改.基本上,对于Linux来说,内存布局约定是从90年代早期的Linux的第一个版本继承而来的.流程必须能够访问以下几个方面:

  • 代码必须在包含起点的范围内.
  • 必须有一个堆栈.
  • 必须有一个堆,有与增加的限制brk()sbrk()系统调用.
  • mmap()系统调用必须有一些空间,包括共享库加载.

如今,堆在哪里malloc(),由mmap()调用支持,这些调用在内核认为合适的任何地址获取内存块.但是在较老的时代,Linux就像以前的类Unix系统一样,它的堆需要一个不间断的大块区域,这可能会增加地址.因此,无论是什么约定,它都必须填充代码并向低地址堆栈,并在给定点之后将每个块的地址空间分配给堆.

但也有堆栈,通常很小但在某些情况下可能会非常显着增长.堆栈增长,当堆栈已满时,我们真的希望流程可以预测崩溃而不是覆盖一些数据.因此,堆栈必须有一个广泛的区域,在该区域的低端,是一个未映射的页面.瞧!在地址零处有一个未映射的页面,用于捕获空指针解引用.因此,定义了堆栈将获得第一个128 MB的地址空间,第一页除外.这意味着代码必须在128 MB之后,地址类似于0x080xxxxx.

正如迈克尔指出的那样,"丢失"128 MB的地址空间并不是什么大问题,因为地址空间非常大,可以实际使用.那时,Linux内核将单个进程的地址空间限制为1 GB,硬件允许的最大值为4 GB,这并不是一个大问题.


小智 6

为什么不从地址0x0开始?至少有两个原因:

  • 因为地址零被称为NULL指针,并且被编程语言用于理智的检查指针.如果您要在那里执行代码,则不能使用地址值.
  • 地址0处的实际内容通常(但不总是)是异常向量表,因此在非特权模式下无法访问.请参阅特定体系结构的文档.

至于入口点_startvs main:如果链接C运行时(C标准库),则库包装名为的函数main,因此它可以在main调用之前初始化环境.在Linux上,这些是应用程序的argcargv参数,env变量,可能还有一些同步原语和锁.它还确保从main传递返回状态代码,并调用_exit终止进程的函数.

  • 在C中,空指针在最低级别上的值可能与0完全不同.在C(源代码)的范围内,机器的无效指针值必须映射为0.从技术上讲,不需要C空指针实际映射到地址零. (5认同)