Структура памяти процесса linux

Структура виртуальной памяти процесса в Linux

Как распределены и по каким регистрам процессора указатели, задающие границы области стека, области динамических данных, области статических данных и области команд модуля программы?

Стоит различать модель памяти языка и организацию памяти непосредственно на целевой машине. Вопрос из заголовка и вопрос в тексте — разные.

1 ответ 1

Вопрос почти не связан с языком C или компилятором GCC, а связан больше с операционной системой, в которой выполняется программа. Конкретно, вас интересует карта виртуальной памяти процесса — именно она определяет, где по каким адресам расположен стек, куча и сегменты исполняемого файла (те самые .bss , .text , .data и прч.). Ниже приведен ответ для ОС с ядром Linux и архитектурой семейства x86.

Ядро Linux делит всё виртуальное адресное пространства процесса на две части: user-space memory и kernel memory. Конкретное деление различно, есть как минимум три варианта:

  1. В архитектуре i386 обычно все виртуальное адресное пространство имеет размер 4 GiB и ядро выделяет нижние 3 GiB для user-space и верхний 1 GiB для самого ядра * .
  2. В архитектуре x86_64 с 4-х уровневыми page tables виртуальное адресное пространство является 48-битным. User-space memory занимает 128 TB, начиная с адреса 0x0000000000000000 и заканчивая адресом 0x00007fffffffffff . Kernel memory также занимает 128 TB, начиная с адреса 0xffff800000000000 и заканчивая адресом 0xffffffffffffffff † .
  3. В архитектуре x86_64 с 5-ти уровневыми page tables виртуальное адресное пространство является 56-битным, а схема его разбиения похожа на (2) † .

Память ядра является одинаковой и разделяемой для всех программ, поэтому нас будет интересовать user-space memory.

Читайте также:  Epson perfection 1270 драйвер linux

Как ни странно, но найти конкретную информацию по разбиению адресного пространства процесса в Linux не так просто. Мне удалось найти достаточно подробную статью “Understanding the Memory Layout of Linux Executables”, где после довольно долго расследования выясняется примерно следующее распределение памяти процесса:

0 Nothing here, because it was just an arbitrary choice by the linker ELF and Program and Section Headers - 0x400000 on 64 bit Program Text (.text) - Entry Point as Reported by readelf Nothing Here either Some unknown assembly and data - 0x600000 Initialised Data (.data) - 0x601068 Uninitialised Data (.bss) - 0x601078 Heap | v Memory Mapped Region for Shared Libraries or Anything Else ^ | User Stack 

Таким образом видим, что стек (англ. stack) располагается в самом конце адресного пространства и расет «вниз» ‡ , то есть по направлению к младшим адресам (к нулю). В свою очередь, куча (англ. heap) растет «вверх» и располагается сразу после секции .bss .

По-умолчанию размер стека равен 8 MiB. Изначально под стек выделяется его первая страница памяти 4 KiB. Если пользовательский код выходит за пределы этих 4 KiB, то происходит Page Fault, которое ловит ядро. Затем ядро проверяет, не вышел ли стек за границу в 8 MiB. Если не вышел — выделяет новую странцу для стека (стек растет), если вышел — убивает процесс.

Размер стека можно менять из пространства пользователя с помощью программы ulimit .

Также стоит отметить, что конкретные адреса кучи и стека всегда будут разными при каждом запуске из-за ASLR (Address Space Layer Randomization). Функции для их рандомизации находятся в файле linux/mm/util.c . Конкретно это функции randomize_stack_top и arch_randomize_brk .

Практический способ выяснения структуры памяти процесса

Другим способом выяснить разметку памяти процесса в Linux будет использование файла /proc//maps , который собственно и содержит информацию об адресном пространстве процесса. Если написать простейший hello world, можно увидеть примерно следующее:

$ cat /proc/$(pidof hello)/maps 558c1ca6f000-558c1ca70000 r--p 00000000 00:20 638 /tmp/hello 558c1ca70000-558c1ca71000 r-xp 00001000 00:20 638 /tmp/hello 558c1ca71000-558c1ca72000 r--p 00002000 00:20 638 /tmp/hello 558c1ca72000-558c1ca73000 r--p 00002000 00:20 638 /tmp/hello 558c1ca73000-558c1ca74000 rw-p 00003000 00:20 638 /tmp/hello 558c1e82c000-558c1e84d000 rw-p 00000000 00:00 0 [heap] 7ff9a4a01000-7ff9a4a03000 rw-p 00000000 00:00 0 7ff9a4a03000-7ff9a4a29000 r--p 00000000 103:04 789879 /usr/lib/libc-2.33.so 7ff9a4a29000-7ff9a4b74000 r-xp 00026000 103:04 789879 /usr/lib/libc-2.33.so 7ff9a4b74000-7ff9a4bc0000 r--p 00171000 103:04 789879 /usr/lib/libc-2.33.so 7ff9a4bc0000-7ff9a4bc3000 r--p 001bc000 103:04 789879 /usr/lib/libc-2.33.so 7ff9a4bc3000-7ff9a4bc6000 rw-p 001bf000 103:04 789879 /usr/lib/libc-2.33.so 7ff9a4bc6000-7ff9a4bd1000 rw-p 00000000 00:00 0 7ff9a4be3000-7ff9a4be4000 r--p 00000000 103:04 789868 /usr/lib/ld-2.33.so 7ff9a4be4000-7ff9a4c08000 r-xp 00001000 103:04 789868 /usr/lib/ld-2.33.so 7ff9a4c08000-7ff9a4c11000 r--p 00025000 103:04 789868 /usr/lib/ld-2.33.so 7ff9a4c11000-7ff9a4c13000 r--p 0002d000 103:04 789868 /usr/lib/ld-2.33.so 7ff9a4c13000-7ff9a4c15000 rw-p 0002f000 103:04 789868 /usr/lib/ld-2.33.so 7ffecbeaf000-7ffecbed0000 rw-p 00000000 00:00 0 [stack] 7ffecbf31000-7ffecbf35000 r--p 00000000 00:00 0 [vvar] 7ffecbf35000-7ffecbf37000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall] 

Здесь видно, что первые пять строк файла означают секции исполняемого файла /tmp/hello . Конкретные названия не указаны, но о значении некоторых секций можно догадаться по выставленным разрешениям (англ. permissions).

Читайте также:  Linux fingerprint reader driver

Так же можно использовать программу-фронтенд pmap :

$ pmap $(pidof hello) 35111: ./hello 000055eef0906000 4K r---- hello 000055eef0907000 4K r-x-- hello 000055eef0908000 4K r---- hello 000055eef0909000 4K r---- hello 000055eef090a000 4K rw--- hello 000055eef116b000 132K rw--- [ anon ] 00007f2b7a11f000 8K rw--- [ anon ] 00007f2b7a121000 152K r---- libc-2.33.so 00007f2b7a147000 1324K r-x-- libc-2.33.so 00007f2b7a292000 304K r---- libc-2.33.so 00007f2b7a2de000 12K r---- libc-2.33.so 00007f2b7a2e1000 12K rw--- libc-2.33.so 00007f2b7a2e4000 44K rw--- [ anon ] 00007f2b7a301000 4K r---- ld-2.33.so 00007f2b7a302000 144K r-x-- ld-2.33.so 00007f2b7a326000 36K r---- ld-2.33.so 00007f2b7a32f000 8K r---- ld-2.33.so 00007f2b7a331000 8K rw--- ld-2.33.so 00007ffd6e1a9000 132K rw--- [ stack ] 00007ffd6e1cb000 16K r---- [ anon ] 00007ffd6e1cf000 8K r-x-- [ anon ] ffffffffff600000 4K --x-- [ anon ] total 2368K 

* В комментарии под одним из вопросов на Unix SE пользователь с ником @phuclv утверждает, что это не совсем верная информация. Согласно комментарию, «в зависимости от версии ядра отношение разбиения может отличаться. Старые версии могут использовать разбиения 1/3, 2/2 или 3/1, что указывается опциями CONFIG_VMSPLIT_ ; а с 2007 года можно выбрать дробные разбиения типа 5/16-тых и 15/32-ых. Если в ручную поменять некоторые #define -ы, можно добиться произвольных разбиений. Сегодня системы, подверженные уязвимости Meltdown обычно используют разбиение 4/4, то есть полностью отдельные адресные пространства для ядра и пользовательского режима».

† Детальное описание карты виртуальной памяти для x86_64 можно найти в документации к ядру.

‡ Направление роста стека на самом деле платформо-зависимо. См. соответствующий вопрос на enSO.

Источник

Оцените статью
Adblock
detector