【问题标题】:Why is the ELF execution entry point virtual address of the form 0x80xxxxx and not zero 0x0?为什么ELF执行入口点虚拟地址的形式是0x80xxxxxx而不是0x0?
【发布时间】:2011-01-12 08:22:56
【问题描述】:

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

到目前为止,我的 Google 研究只是让我得出一些(模糊的)历史推测,如下所示:

民间传说 0x08048000 曾经是 STACK_TOP(即堆栈从 0x08048000 附近向下增长到 0)在 *NIX 到 i386 的端口上,这是由加利福尼亚州圣克鲁斯的一个团体颁布的。当时 128MB 的 RAM 很昂贵,而 4GB 的 RAM 是不可想象的。

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

【问题讨论】:

标签: point elf virtual-address-space


【解决方案1】:

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

起始地址是一组描述内存布局方式的约定的一部分。链接器在生成可执行二进制文件时,必须知道这些约定,因此它们不太可能改变。基本上,对于 Linux,内存布局约定是从 90 年代初的 Linux 的第一个版本继承而来的。一个进程必须可以访问多个区域:

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

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

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

正如 Michael 指出的那样,“丢失” 128 MB 的地址空间没什么大不了的,因为地址空间对于实际使用的内容来说非常大。当时,Linux内核将单个进程的地址空间限制为1 GB,硬件允许的最大4 GB,这并没有被认为是一个大问题。

【讨论】:

    【解决方案2】:

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

    • 因为地址 0 被称为 NULL 指针,编程语言使用它来完善检查指针。如果要在那里执行代码,则不能为此使用地址值。
    • 地址 0 的实际内容通常(但不总是)是异常向量表,因此在非特权模式下无法访问。请查阅您的特定架构的文档。

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

    【讨论】:

    • 在 C 中,空指针的值可能与最低级别的 0 完全不同。在 C(源代码)的范围内,机器的无效指针值必须映射到 0。从技术上讲,C 空指针实际上不需要映射到地址 0。
    • _GLOBAL_OFFSET_TABLE_ 也指向 Binutils 2.24 中的 0x200XXX 范围。
    • @datenwolf 这是一个有争议的问题,所有现代处理器都将地址表示为二进制补码整数,将NULL 表示为0 以外的任何其他值,在这种情况下将是毫无意义的性能损失。仅仅因为标准允许它并不意味着它是一个好主意。即使在内存非常有限的嵌入式环境中,0x00 通常也会保留给NULL
    • @yyny:有些东西是 8086 实模式……另外,我指的不是 2s 补全与其他类型的数字表示,而是 trap 值。
    • @datenwolf 我知道没有针对 8086 的 C 编译器。这就是我的全部观点。 ANSI C 标准在编写时考虑到了前向兼容性,当时仍可以想象分段内存将变得司空见惯。现在几乎每个处理器都使用基于二进制补码的整数寻址,这意味着没有实际理由将NULL 表示为非零值。 C 在这一点上已经超过 30 年了,并且已经很好地同意将 NULL 指针常量转换为整数会产生 0 的值。
    猜你喜欢
    • 2021-01-06
    • 2019-05-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-29
    • 2011-08-15
    • 2020-11-28
    相关资源
    最近更新 更多