Как сделать текстовый сегмент доступным только для чтения? - PullRequest
1 голос
/ 23 января 2020

Я знаю, что текстовый сегмент является сегментом только для чтения, и попытка записи в него приводит к «Ошибка шины». Мне интересно, как этот сегмент сделан только для чтения.

Поскольку физическая память не доступна только для чтения, это необходимо сделать во время подкачки.

Есть ли бит для каждой страницы памяти для страниц только для чтения, заданных для текстового сегмента?

Ответы [ 3 ]

2 голосов
/ 23 января 2020

Файл ELF (исполняемый файл Unix или разделяемый объект) имеет две основные концепции:

Раздел: Область внутри исполняемого файла с определенной ролью c , Внутри файла ELF могут быть разные разделы (можно увидеть в man elf ). Общие разделы в файле ELF:

  • .text (SHT_PROGBITS): фактический исполняемый код в файле ELF.
  • .dynsym (SHT_DYNSYM): содержит информацию о символах который должен быть извлечен динамически.
  • .rela.dyn и .rela.plt (SHT_RELA): содержит информацию о перемещении для динамического компоновщика c, используемого при загрузке файла ELF в память.
  • .dynamic (SHT_DYNAMI C): содержит информацию для динамического компоновщика c, такую ​​как другие зависимости, смещения для различных секций во время выполнения и т. Д. c.
  • .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

Сегмент: Область внутри исполняемого файла с loa d инструкция для динамического компоновщика c. Это означает, что сегмент - это просто область внутри ELF-файла, которая должна быть загружена в предпочтительную адресную память и имеет определенные c права доступа, выравнивание и т. Д. c.

Каждый раздел (который является областью в файле 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 

Здесь мы видим, что все разделы, связанные с исполняемым кодом, а также многие разделы, связанные с динамической загрузкой файла c, находятся в сегменте 00 (PT_LOAD), который имеет права на чтение и выполнение (R E). Разделы, которые должны быть изменены загрузчиком, находятся в сегменте 01 (PT_LOAD), который имеет разрешения на чтение и запись (RW). Сегмент 02 имеет тип PT_DYNAMI C и содержит динамическую c информацию о связывании - секцию .dynamic.

Линкер Dynami c учитывает всю эту информацию, когда загружает файл ELF в память , Он загружает различные сегменты файла ELF с диска в память и защищает их страницы с правильными разрешениями. Затем он перебирает различные разделы и использует их в соответствии с их ролями (перемещения, разрешение динамических c символов и т. Д. c ...).

Сама защита памяти создается ОС и само оборудование. Это похоже на использование Linux методов mprotect () . Более подробную информацию о защите памяти можно найти здесь .

1 голос
/ 23 января 2020

Как правило, это происходит в том случае, если загрузчик программы выделяет память для текста программы, считывает в нее текст программы из исполняемого файла и просит операционную систему сделать эту память доступной только для чтения. Операционная система записывает это в свои базы данных и передает информацию аппаратному обеспечению, чтобы запретить запись в эту память.

Другая возможность состоит в том, что загрузчик фактически не считывает текст программы в память, а просто выдает запрос к операционной системе отметить, что соответствующая часть исполняемого файла «отображена» в памяти как доступная только для чтения. Когда процесс на самом деле пытается выполнить код на странице памяти, операционная система считывает его в память (с правом записи для себя, так что он может это сделать) и помечает его как доступный только для чтения (для процесса).

Этот последний случай является оптимизацией, поскольку он позволяет запускать вашу программу, не загружая ее в первую очередь. И его части, которые никогда не используются, такие как код для обработки редких ошибок, могут никогда не загрузиться.

0 голосов
/ 24 января 2020

Так не должно быть. Вы можете заставить текстовый сегмент программы читать и писать. Но вы должны проинструктировать компоновщика об этом. Операционная система генерирует страницы, которые генерируют ошибку страницы, когда вы пытаетесь писать на них, но не когда вы пытаетесь читать. Это часть многопользовательской системы защиты (также называемой системой защиты памяти).

Причина, по которой текстовый сегмент только для чтения, является наследием древних времен. Несколько экземпляров одной программы, вызываемой разными процессами (например, разные пользователи делают ls, чтобы показать свое содержимое каталога), используют один и тот же сегмент как текст, поэтому почему бы не разделить этот сегмент и не загружать его каждый раз для каждого экземпляра? Это причина, но если вы хотите изменить текстовый сегмент вашей программы во время выполнения, вы можете назначить его COW ((C) opy (o) n (w) rite) и страницы будет копироваться индивидуально, когда вы пытаетесь писать на них, поэтому у вас есть иллюзия, что это чтение / запись.

...