如何将文本段设为只读?

San*_*ndy 3 c linux operating-system

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

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

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

小智 6

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

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

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

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

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

readelf --sections -W <file>
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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

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

使用readelf一个可以看到ELF文件中的所有段:

readelf --segments -W <file>
Run Code Online (Sandbox Code Playgroud)

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

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 
Run Code Online (Sandbox Code Playgroud)

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

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

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