【问题标题】:How is text segment made read-only?文本段如何设置为只读?
【发布时间】:2020-05-09 06:24:48
【问题描述】:

我知道文本段是只读段,尝试写入会导致“总线错误”。 我很好奇这个段是如何变成只读的。

由于物理内存不是只读的,这必须在分页期间完成。

为文本段设置的只读页面的每一页内存是否有位?

【问题讨论】:

  • 取决于架构。您在哪里看到总线错误?
  • 页表项中有权限标志。
  • 运行时加载器负责设置地址映射,包括为每个加载的段提供不同的保护。操作系统负责在页面级别执行保护。一个非常概括的观点。
  • 内存保护系统知道进程可以读取哪些页面,以及进程可以写入哪些页面。发生违规时会通知内核(例如,这可能会导致它交换页面或发送 SIGSEGV 信号)。阅读内存虚拟化/虚拟内存。

标签: c linux operating-system


【解决方案1】:

不必如此。您可以使程序的文本段读写。但是您必须告知链接器。操作系统生成的页面会在您尝试写入时产生页面错误,但不会在您尝试读取时产生页面错误。这是多用户保护系统(也称为内存保护系统)的一部分。

文本段被只读的原因是古代遗留下来的。由不同进程调用的单个程序的多个实例(例如,不同的用户使用ls 来显示他们自己的目录内容)使用相同的段作为文本,所以为什么不共享该段并避免每次都加载它对于每个实例? 这就是原因,但是如果您想在运行时修改程序的文本段,您可以将其指定为 COW ((C)opy (o)n (w)rite),页面将当您尝试在它们上写入时被单独复制,因此您有读/写的错觉。

【讨论】:

    【解决方案2】:

    ELF 文件(Unix 可执行文件或共享对象)有两个主要概念:

    部分:可执行文件中具有特定角色的区域。 ELF 文件中可能有不同的部分(可以在man elf 中看到)。 ELF 文件中的常见部分是:

    • .text (SHT_PROGBITS):ELF 文件中的实际可执行代码。
    • .dynsym (SHT_DYNSYM):保存有关应动态检索的符号的信息。
    • .rela.dyn.rela.plt (SHT_RELA):保存重定位信息,供动态链接器在将 ELF 文件加载到内存时使用。
    • .dynamic (SHT_DYNAMIC):保存动态链接器的信息,例如其他依赖项、运行时不同部分的偏移量等。
    • .symtab (SHT_SYMTAB):保存符号表。
    • .strtab (SHT_STRTAB):保存一个字符串表。

    还有更多的部分,以上只是一些常见的部分。

    使用readelf可以查看ELF文件中的所有部分:

    readelf --sections -W <file>
    

    在我计算机中的共享对象上运行此命令会产生以下输出(简化):

    There are 29 section headers, starting at offset 0x1898:
    
    Section Headers:
      [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
    ...
      [ 3] .dynsym           DYNSYM          0000000000000230 000230 000168 18   A  4   2  8
      [ 4] .dynstr           STRTAB          0000000000000398 000398 0000b0 00   A  0   0  1
    ...
      [ 7] .rela.dyn         RELA            0000000000000488 000488 0000c0 18   A  3   0  8
      [ 8] .rela.plt         RELA            0000000000000548 000548 000030 18  AI  3  22  8
    ...
      [12] .text             PROGBITS        00000000000005e0 0005e0 000121 00  AX  0   0 16
    ...
      [20] .dynamic          DYNAMIC         0000000000200e18 000e18 0001c0 10  WA  4   0  8
    ...
      [23] .data             PROGBITS        0000000000201028 001028 000008 00  WA  0   0  8
    ...
      [27] .symtab           SYMTAB          0000000000000000 001068 000570 18     28  45  8
      [28] .strtab           STRTAB          0000000000000000 0015d8 0001c6 00      0   0  1
    

    段: 可执行文件内的一个区域,其中包含动态链接器的加载指令。意思是,段只是 ELF 文件中的一个区域,应该加载到首选地址内存中,并具有特定的权限、对齐方式等。

    每个部分(ELF 文件中具有逻辑角色的区域)都应该是段的一部分,具有正确的权限和特征。一个段内可以有多个部分,而一个部分位于一个段内(一对多关系)。

    使用readelf可以查看ELF文件中的所有段:

    readelf --segments -W <file>
    

    在我计算机中的共享对象上运行此命令会产生以下输出:

    There are 7 program headers, starting at offset 64
    
    Program Headers:
      Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
      LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x00079c 0x00079c R E 0x200000
      LOAD           0x000e00 0x0000000000200e00 0x0000000000200e00 0x000230 0x000238 RW  0x200000
      DYNAMIC        0x000e18 0x0000000000200e18 0x0000000000200e18 0x0001c0 0x0001c0 RW  0x8
      NOTE           0x0001c8 0x00000000000001c8 0x00000000000001c8 0x000024 0x000024 R   0x4
      GNU_EH_FRAME   0x000718 0x0000000000000718 0x0000000000000718 0x00001c 0x00001c R   0x4
      GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10
      GNU_RELRO      0x000e00 0x0000000000200e00 0x0000000000200e00 0x000200 0x000200 R   0x1
    
     Section to Segment mapping:
      Segment Sections...
       00     .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame 
       01     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 
       02     .dynamic 
       03     .note.gnu.build-id 
       04     .eh_frame_hdr 
       05     
       06     .init_array .fini_array .jcr .dynamic .got 
    

    在这里,我们可以看到所有与可执行代码相关的部分,以及许多与文件动态加载相关的部分都在段00(PT_LOAD)中,该段具有读取和可执行权限(R E)。应该由加载程序修改的部分位于具有读写权限的段 01 (PT_LOAD) (RW)。段 02 的类型为 PT_DYNAMIC,并保存动态链接信息 - .dynamic 部分。

    动态链接器在将 ELF 文件加载到内存时会考虑所有这些信息。它将 ELF 文件的不同段从磁盘加载到内存中,并使用正确的权限保护它们的页面。然后,它遍历不同的部分,并根据它们的角色(重定位、解析动态符号等)使用它们。

    内存保护本身是由操作系统和硬件本身进行的。它类似于使用 Linux 方法mprotect()。更多关于内存保护的信息可以在here找到。

    【讨论】:

    • 我有一个包含 25 个部分但零个部分的目标文件。这怎么可能?
    • 你能分享readelf --segments &lt;YOUR ELF FILE&gt;的输出吗?
    【解决方案3】:

    通常,发生这种情况的一种方式是程序加载器为程序文本分配内存,从可执行文件中将程序文本读入其中,并要求操作系统将该内存设为只读。操作系统将此记录在其数据库中,并将信息传送给硬件,告诉它不允许写入该内存。

    另一种可能性是加载程序实际上并未将程序文本读入内存,而只是向操作系统发出请求,以将可执行文件的适当部分“映射”到内存中,标记为只读。当进程实际尝试执行内存页面中的代码时,操作系统会将其读入内存(对自己具有写访问权限,因此它可以这样做)并将其标记为只读(对进程而言)。

    后一种情况是一种优化,因为它允许您的程序在不先加载所有程序的情况下启动。而其中一些从未使用过的部分,例如处理罕见错误的代码,可能永远不会被加载。

    【讨论】:

    • @Sandy:不同的硬件有不同的处理方式。通常有各种格式的页表条目,它们告诉硬件某些虚拟内存地址映射到具有某些权限的某个物理内存地址,有时还包含有关哪些进程的信息。页表条目可以通过将它们放在特殊的内存位置或特殊寄存器中或通过使用特殊指令来传送到硬件,具体取决于硬件。
    猜你喜欢
    • 2020-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-09
    相关资源
    最近更新 更多