Память процесса

Самые первые версии Unix работали на компьютерах без аппаратной поддержки виртуальной памяти, однако очень скоро такая поддержка появилась в большинстве процессорных архитектур и стала обязательным условием для возможности запуска Unix/Linux на соответствующей платформе. В линейке Intel аппаратная поддержка виртуальной памяти появилась в процессорах i286. С выходом этих процессоров на рынок связано появление лицензионной версии Unix для ПК - Xenix от Microsoft.

Страничная организация памяти

Несмотря на то, что современные процессоры адресуют память с точностью до байта, основными единицами управления памятью в Unix являются страницы.

Страница - это минимальная единица отображения непрерывного диапазона адресов виртуальной памяти на непрерывный диапазон адресов физической памяти. Соответственно, выделение физической память для данных процессов и ядра, а так же буферов дискового кеша и буферов для получения данных с устройств по DMA всегда выполняется страницами.

Страничная организация памяти поддерживается специализированным аппаратным устройством - модулем управления памятью (MMU, Memory Management Unit), которое выполняет преобразование виртуальных адресов в физические. MMU может быть интегрировано в процессор или входить в чипсет.

Размеры страниц отличаются для различных аппаратных платформ.Можно считать, что для 32-разрядных аппаратных платформ размер страницы равен 4 Кбайта, а для 64-разрядных платформ - 8 Кбайт. Современные процессоры от Intel позволяют выбирать размер страницы, и, соответственно, ядро Linux позволяет управлять этим выбором.

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

Для каждого процесса в момент его создания инициализируется своя таблица страниц. Таким образом одни и те же адреса в виртуальной памяти различных процессов ссылаются на различные адреса физической памяти. Благодаря этому, физические страницы памяти одного процесса становятся недоступными для других процессов. Таблица страниц ядра отображает часть адресов на физическую память, в которой расположено само ядро, а вторую часть - на страницы памяти какого-либо процесса. Такая таблица позволят ядру получить доступ к физической памяти процессов для копирования входных/выходных данных системных вызовов. Например, при вызове read(fd,buf,size) код в ядре имеет доступ к буферу buf для заполнения его данными, прочитанными из файла.

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

Интересное замечание: существуют процессорные архитектуры, в которых физическая память может быть больше виртуальной. Так серверные материнские платы на процессорах x32 позволяют устанавливать больше чем 4 Гбайта оперативной памяти, в этом случае ядру приходится модифицировать часть собственной таблицы страниц, чтобы получить доступ к нужным участкам физической памяти.

Часть ссылок в таблице страниц может иметь специальное значение, указывающее на то, что страница не отображается на физическую память. Данный механизм используется для реализации файлов подкачки (swap). При попытке обращения к такой "несуществующей" странице, возникает аппаратное прерывание, которое обрабатывается в ядре. Код ядра может выделить процессу свободную страницу физической памяти, загрузить в неё данные из файла подкачки, поменять таблицу страниц так, чтобы требуемая виртуальная память отображалась на свежеподготовленную физическую ти вернуть управления процессу. Выполнение процесса начнётся с той же инструкции доступа к памяти, на которой возникло прерывание, но теперь эта инструкция выполнится нормально, поскольку изменилась ТС.

В некоторых случаях возможно отображение одной физической страницы памяти в виртуальную память нескольких процессов. Такое совместное использование физической памяти называется разделяемой памятью (shared memory). Данный механизм используется для обмена данными между процессами и требует аппаратной или программной поддержки в виде семафоров, для синхронизации параллельного доступа к памяти.

Часть физической памяти используется для хранения файлового кеша. В кеше хранится блоки данных файла размерами кратными странице памяти. При операциях считывания/записи в ядре происходит копирование нужного числа страниц между памятью процесса и кешем, а операции с файловой системой откладываются по времени. Если физическую память требуется освободить, то она сохраняется в файл, если кто-то обращается к странице, отсутствующей в кеше, она считывается из файла. Как правило, страницы кеша доступны только ядру, однако существует несколько случаев, когда они отображаются непосредственно в виртуальную память процессов. Механизм отображения страниц кеша в память процесса называется отображением файлов в память (memory mapped file). Данный механизм может использоваться как альтернатива разделяемой памяти (анонимные файлы в памяти), а так же как механизм совместного использования кода программ, путём отображения секции кода из исполняемого файла в виртуальное адресное пространство процессов, которые этот код исполняют.

При создании нового процесса вызовом fork()Linux фактически вызывается clone()) таблица страниц копируется в дочерний процесс и некоторое время родительский и дочерний процесс совместно используют общую физическую память. В дальнейшем, при изменении какой-либо страницы в одном из двух процессов для этого процесса создаётся копия страницы в физической памяти и соответственно изменяется таблица страниц. Данный механизм позволяет экономить время при создании нового процесса. Этот механизм называется "копирование при записи" (Copy on write, COW).

Созданиие новых потоков в Linux вызовом clone() аналогично созданию нового процесса, но дочерний процесс наследует от родительского процесса таблицу страниц. Таким образом, потоки в Linux можно рассматривать как дочерние процессы, использующие общую с родительским процессом виртуальную память. Главным отличием между процессами является размещение стека в виртуальной памяти.

Вызов execve("prog_file"), загружающий в память процесса новую программу, полностью изменяет таблицу страниц, освобождая занятую физическую память и выделяя новую, которая инициализируется в соответвии с данными, хранящимися в исполняемом файле.

Области памяти

Вся виртуальная память процесса в Linux поделена на несколько областей (сегментов), различного назначения. Выделяются следующие области:

  • сегмент кода, содержащий инструкции процессора, который отображается на соответствующие области исполняемого файла. Традиционно обозначается как text.
  • сегмент инициализированных глобальных переменных, которые изначально загружаются из исполняемого файла. Обозначаются как data.
  • сегмент неинициализированных глобальных переменных, который изначально заполнен нулями. Традиционно обозначается BSS (Block Started by Symbol). Поскольку хранить в исполняемом файле нули не имеет смысла, то в нём хранится имя переменной (symbol) и её размер, а выделение памяти и инициализация нулями, происходит в момент запуска программы.
  • сегмент стека - Stack.
  • область кучи, для динамического выделения памяти функциями malloc(), new() и т.п. - Heap.
  • дополнительные сегменты кода, данных и BSS для каждой разделяемой между процессами библиотеки, такой как библиотека libc. Динамические библиотеки в Linux имеют суффикс в имени файла - .so, что означает shared object.
  • область для отображения в память файлов.
  • область для отображения разделяемой памяти (shared memory).
  • в современном Linux в отдельные области памяти vsyscall и vdso (virtual dynamic shared object) вынесен часто вызываемый код ядра, не требующий системных полномочий. В качестве примера такого обычно приводят вызов gettimeofday(). Вынесение кода в виртуальную память пространства помогает сэкономить время, расходуемое на изменение таблицы страниц во время системного вызова.

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

Флаги, описывающие права доступа к страницам, входящих в область:

  • VM_READ - страницы памяти можно читать
  • VM_WRITE - в страницы памяти можно писать
  • VM_EXEC - можно выполнять код, хранящийся в страницах памяти
  • VM_SHARED - изменения страницы памяти будут видны всем процессами, которые совместно используют эту страницу. Если этот флаг не выставлен, то память считается "приватной" и, при первой записи в неё, процессу будет создана персональная копия страницы в физической память.

Просмотреть список областей процесса можно в файле /proc//maps

Пример вывода программой cat собственных областей памяти. Буква p в флагах доступа означает private. Если бы область использовалась совместно, то стояла бы буква s -shared.

$ cat /proc/self/maps
00400000-0040b000 r-xp 00000000 fd:01 134395423                          /usr/bin/cat
0060b000-0060c000 r--p 0000b000 fd:01 134395423                          /usr/bin/cat
0060c000-0060d000 rw-p 0000c000 fd:01 134395423                          /usr/bin/cat
017f1000-01812000 rw-p 00000000 00:00 0                                  [heap]
7f5f529df000-7f5f58f08000 r--p 00000000 fd:01 67442653                   /usr/lib/locale/locale-archive
7f5f58f08000-7f5f590cb000 r-xp 00000000 fd:01 201328510                  /usr/lib64/libc-2.17.so
7f5f590cb000-7f5f592ca000 ---p 001c3000 fd:01 201328510                  /usr/lib64/libc-2.17.so
7f5f592ca000-7f5f592ce000 r--p 001c2000 fd:01 201328510                  /usr/lib64/libc-2.17.so
7f5f592ce000-7f5f592d0000 rw-p 001c6000 fd:01 201328510                  /usr/lib64/libc-2.17.so
7f5f592d0000-7f5f592d5000 rw-p 00000000 00:00 0
7f5f592d5000-7f5f592f7000 r-xp 00000000 fd:01 201328504                  /usr/lib64/ld-2.17.so
7f5f594e8000-7f5f594eb000 rw-p 00000000 00:00 0
7f5f594f5000-7f5f594f6000 rw-p 00000000 00:00 0
7f5f594f6000-7f5f594f7000 r--p 00021000 fd:01 201328504                  /usr/lib64/ld-2.17.so
7f5f594f7000-7f5f594f8000 rw-p 00022000 fd:01 201328504                  /usr/lib64/ld-2.17.so
7f5f594f8000-7f5f594f9000 rw-p 00000000 00:00 0
7ffc13623000-7ffc13644000 rw-p 00000000 00:00 0                          [stack]
7ffc13785000-7ffc13787000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

Прикрепленный файлРазмер
Иконка изображения memory-maps.png33.08 КБ

Распределение адресов в памяти процесса Linux i386

Адресация в архитектуре i386

Подробности связанные с аппаратной поддержкой управления памятью в процессорах, совместимых с i386, можно посиотреть по ссылке. Ссылка: Исследование модели памяти Linux

Распределение адресов в памяти процесса Linux i386

Размеры сегментов text, data и bss исполняемого файла можно посмотреть командой size:

$ size /bin/ls
   text    data     bss     dec     hex filename
 103119    4768    3360  111247   1b28f /bin/ls

В новых версиях Linux положение стека и кучи рандомизируется случайными смещениями, а область Mmap растёт вниз. Ссылка: Организация памяти процесса

Тестовая программа

Тестовая программа

#include <unistd.h>
#include <malloc.h>
#include <stdio.h>
#include <sys/mman.h>

int bss, data=10;
int main(int argc, char *argv[])
{
  int stack;
  void *heap, *brk, *mmp;
  heap=malloc(1);
  brk=sbrk(0);
  mmp=mmap(0,1,PROT_READ,MAP_SHARED|MAP_ANONYMOUS,-1,0);
  printf("Text=%p\nData=%p\nBSS=%p\nHeap=%p\nBrk=%p\nlibc.so=%p\nMmap=%p\nStack=%p\nArgv=%p\n ",main,&data,&bss,heap,brk,printf,mmp,&stack,argv);
  sleep(100);
  return 0;
}

Вывод тестовой программы

$ ./testmap
Text   =0x0045c690
Data   =0x0045e02c
BSS    =0x0045e034
Heap   =0x01435008
Brk    =0x01456000
libc.so=0xb75bb940
Mmap   =0xb773a000
Stack  =0xbfe8f850
Argv   =0xbfe8f924

Распределение памяти тестовой программы

$ ./testmap >/dev/null &
[1] 6662

$ cat /proc/6662/maps 
0042a000-0042b000 r-xp 00000000 08:01 305009     /home/student/testmap
0042b000-0042c000 r--p 00000000 08:01 305009     /home/student/testmap
0042c000-0042d000 rw-p 00001000 08:01 305009     /home/student/testmap
00ece000-00eef000 rw-p 00000000 00:00 0          [heap]
b7603000-b77b4000 r-xp 00000000 08:01 151995     /lib/i386-linux-gnu/libc-2.24.so
b77b4000-b77b5000 ---p 001b1000 08:01 151995     /lib/i386-linux-gnu/libc-2.24.so
b77b5000-b77b7000 r--p 001b1000 08:01 151995     /lib/i386-linux-gnu/libc-2.24.so
b77b7000-b77b8000 rw-p 001b3000 08:01 151995     /lib/i386-linux-gnu/libc-2.24.so
b77b8000-b77bb000 rw-p 00000000 00:00 0 
b77cb000-b77cc000 r--s 00000000 00:05 100324     /dev/zero (deleted)
b77cc000-b77ce000 rw-p 00000000 00:00 0 
b77ce000-b77d0000 r--p 00000000 00:00 0          [vvar]
b77d0000-b77d2000 r-xp 00000000 00:00 0          [vdso]
b77d2000-b77f5000 r-xp 00000000 08:01 138298     /lib/i386-linux-gnu/ld-2.24.so
b77f5000-b77f6000 r--p 00022000 08:01 138298     /lib/i386-linux-gnu/ld-2.24.so
b77f6000-b77f7000 rw-p 00023000 08:01 138298     /lib/i386-linux-gnu/ld-2.24.so
bff5d000-bff7e000 rw-p 00000000 00:00 0          [stack]


$ uname -a
Linux antix-1 4.9.87-antix.1-486-smp #1 SMP Tue Mar 13 12:29:54 EDT 2018 i686 GNU/Linux

Управление областями памяти

Стек процесса

Вообще говоря, стеков в процессе может быть много и размещаться они могут в разных областях виртуальной памяти. Вот несколько примеров:

Cтек основной нити.

Он же стек процесса в однопоточном приложении. Начальный адрес выделяется ядром. Размер стека может быть изменён в командной строке до запуска программы командой ulimit -s <размер в килобайтах> или вызовом setrlimit

#include <sys/resource.h>
...
struct rlimit rl;
int err;

rl.rlim_cur = 64*1024*1024;
err = setrlimit(RLIMIT_STACK, &rl);

Стек обработчика сигналов

Стек обработчика сигналов может быть расположен в любой области памяти, доступной на чтение и запись. Альтернативный стек создаётся вызовом sigaltstack(new, old), которому передаются указатели на структуры, описывающие стек:

typedef struct {
  void  *ss_sp;     /* Base address of stack */
  int    ss_flags;  /* Flags */
  size_t ss_size;   /* Number of bytes in stack */
} stack_t;

После создания альтернативного стека можно задавать обработчик сигнала, указав в параметрах функции sigaction() флаг SS_ONSTACK.

Стек нити

При создании новой нити для всегда создаётся новый стек. Функция инициализации нити pthread_attr_init(pthread_attr_t *attr) позволяет вручную задать базовый адрес стека нити и его размер через поля attr.stackaddr и attr.stacksize. В большинстве случаев рекомендуется предоставить выбор адреса и размера стека системе, задав attr.stackaddr=NULL; attr.stacksize=0;.

Выделение памяти из кучи

Динамическое выделение памяти в куче (heap) реализовано на уровне стандартных библиотек C/C++ (функция malloc() и оператор new соответственно). Для распределения памяти из кучи процесс должен сообщить ядру, какой размер виртуальной памяти должен быть отображён на физическую память. Для этого выделяется участок виртуальной памяти, расположенный между адресами start_brk и brk. Величина start_brk фиксирована, а brk может меняться в процессе выполнения программы. Brk (program break - обрыв программы) - граница в виртуальной памяти на которой заканчивается отображение в физическую память. В современном Linux за этой границей могут быть отображения файлов и кода ядра в память процесса, но в оригинальном Unix это был "край" памяти программы. Начальное значение brk - start_brk устанавливается в момент загрузки программы из файла вызовом execve() и указывает на участок после инициализированных (data) и неинициализированных (BSS) глобальных переменных.

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

 #include <unistd.h>
 int brk(void *addr); //явное задание адреса
 void *sbrk(intptr_t increment); //задание адреса относительно текущего значения
                                 // возвращает предыдущее значение адреса границы 

Вызов brk() устанавливает максимальный адрес виртуальной памяти, для которого в сегменте данных выделяется физическая память. Увеличение адреса равноценно запросу физической памяти, уменьшение - освобождению физической памяти.

Вызов sbrk(0) позволяет узнать текущую границу сегмента памяти.

В Linux вызов brk() транслируется в вызов функции ядра do_mmap(), изменяющий размер анонимного файла, отображаемого в память.

do_mmap(NULL, oldbrk, newbrk-oldbrk,
           PROT_READ|PROT_WRITE|PROT_EXEC,
           MAP_FIXED|MAP_PRIVATE, 0)

Файлы, отображаемые в память

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

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
           int fd, off_t offset);
int msync(void *addr, size_t length, int flags);

int munmap(void *addr, size_t length);

Функция mmap() отображает length байтов, начиная со смещения offset файла, заданного файловым дескриптором fd, в память, начиная с адреса addr. Параметр addr является рекомендательным, и обычно бывает выставляется в NULL. mmap() возвращает фактический адрес отображения или значение MAP_FAILED (равное (void *) -1) в случае ошибки.

Аргумент prot описывает режим доступа к памяти (не должен конфликтовать с режимом открытия файла).

  • PROT_EXEC данные в отображаемой памяти могут исполняться
  • PROT_READ данные можно читать
  • PROT_WRITE в эту память можно писать
  • PROT_NONE доступ к этой области памяти запрещен.

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

  • MAP_SHARED запись в эту область памяти будет эквивалентна записи в файл и изменения доступны другим процессам. Файл может не обновляться до вызова функций msync() или munmap().
  • MAP_PRIVATE неразделяемое отображение с механизмом copy-on-write. Запись в эту область приводит к созданию для процесса персональной копии данных в памяти и не влияет на файл.
  • MAP_ANONYMOUS память не отображается ни в какой файл, аргументы fd и offset игнорируются. Совместно с MAP_SHARED может использоваться для создания общей памяти, разделяемой с дочерними процессами
  • MAP_FIXED память обязательно отображается с адреса addr или возвращается ошибка. Не рекомендуется к использованию, так как сильно зависит от архитектуры процессора и конкретной ОС.

msync() сбрасывает изменения сделанные в памяти в отображенный файл. Параметры addr и length позволяют синхронизировать только часть отображенной памяти.

munmap() отменяет отображение файла в память и сохраняет сделанные в памяти изменения в файл.

Разделяемая память в System V IPC

В подсистеме межпроцессного взаимодействия System V IPC (System five interprocess communication - названо в по имени версии Unix, в которой эта подсистема появилась) для совместной работы с памятью используется резервирование физической памяти на уровне ядра. После резервирования процессы могут отображать зарезервированную память в своё виртуальное адресное пространство используя для идентификации зарезервированного участка идентификатор, генерируемый специальным образом.

Данное резервирование не привязано к какому-либо процессу и сохраняется даже тогда, когда ни один процесс эту физическую память не использует. Таким образом выделение памяти для совместного использования становится "дорогим" с точки зрения использованных ресурсов. Программа, резервирующая области памяти для совместного доступа и не освобождающая их, может либо исчерпать всю доступную память, либо занять максимально доступное в системе количество таких областей.

У областей совместно используемой памяти, как и других объектов System V IPC есть атрибуты "пользователь-владелец", "группа-владельца", "пользователь-создатель", "группа-создателя", а так же права на чтение и запись для владельца, группы-владельца и остальных, аналогичные файловым. Например: rw- r-- ---.

Пример вызовов для работы общей памятью:

// создание ключа на основе inode файла и номера проекта
// файл не ложен удаляться до завершения работы с общей памятью
key_t key=ftok("/etc/hostname", 456);

// получение идентификатора общей памяти на основе ключа
// размером size байт с округлением вверх
// до размера кратного размеру страницы
//
// опция IPC_CREAT - говорит, что если память ещё не зарезервирована
// то должна быть выполнена резервация физической памяти 
size=8000
int shmid=shmget(key, size, IPC_CREAT); 

// подключение зарезервированной памяти к виртуальному адресному пространству
// второй параметр - желаемый начальный адрес отображения
// третий параметр - флаги, такие как SHM_RDONLY
int *addr=(int *)shmat(shmid, NULL, 0); 

// можно работать с памятью по указателю
addr[10]=23;

// отключение разделяемой памяти от виртуального адресного пространства
int err;
err=shmdt(addr); 

// освобождение зарезервированной памяти
err=shmctl(shmid, IPC_RMID, NULL);

Список всех зарезервированных областей памяти в системе можно просмотреть командой lspci -m:

 lsipc -m
KEY        ID          PERMS OWNER  SIZE NATTCH STATUS CTIME  CPID  LPID COMMAND
0xbe130fa1 3112960 rw-------  root 1000B     11        May03  7217  9422 /usr/sbin/httpd -DFOREGROUND
0x00000000 557057  rw------- usr74  384K      2 dest   Apr28 17752  7476 kdeinit4: konsole [kdeinit]
0x00000000 5898243 rw------- usr92  512K      2 dest   12:05  5265  9678 /usr/bin/geany /home/s0392/1_1.s
0x00000000 4521988 rw------- usr75  384K      2 dest   May06 22351 16323 sview
0x00000000 3276805 rw------- usr15  384K      1 dest   May05 24835 15236
0x00000000 4587530 rw------- usr75    2M      2 dest   May06 19404 16323 metacity 

OOM Killer

Выделение физической памяти в Linux оптимистично. В момент вызова brk() проверяется лишь то факт, что заказанная виртуальная память не превышает общего объёма физической памяти + размер файла подкачки. Реальное выделение памяти происходит при первой записи. В этом случае может оказаться, что вся доступная физическая память и своп уже распределены между другими процессами.

При нехватке физической памяти Linux запускает алгоритм Out of memory killer (OOM killer) который относительно случайно выбирает один из процессов и завершает его, освобождая тем самым физическую память.

Формат исполняемых файлов

Тестовая программа

Программа size выдает размер секций исполняемого файла. По умолчанию выдаётся суммарный размер по типам в формате Berkley:

$ size /bin/ls
   text    data     bss     dec     hex filename
 103119    4768    3360  111247   1b28f /bin/ls

Опция -A выдает все секции в формате SystemV:

$ size -A  /bin/ls
/bin/ls  :
section                size      addr
.interp                  28   4194872
.note.ABI-tag            32   4194900
 ...
.init                    26   4202832
.plt                   1808   4202864
.plt.got                 24   4204672
.text                 65866   4204704
.fini                     9   4270572
...
.data                   576   6402976
.bss                   3360   6403552
.gnu_debuglink           16         0
.gnu_debugdata         3296         0
Total                114559

Заголовок ELF

$ objdump -f ./mmp

./mmp:     file format elf32-i386
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x080483e0

Заголовки секций

$ objdump -h ./mmp

./mmp:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .interp       00000013  08048154  08048154  00000154  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .note.ABI-tag 00000020  08048168  08048168  00000168  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .note.gnu.build-id 00000024  08048188  08048188  00000188  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .gnu.hash     00000024  080481ac  080481ac  000001ac  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .dynsym       00000090  080481d0  080481d0  000001d0  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .dynstr       00000063  08048260  08048260  00000260  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .gnu.version  00000012  080482c4  080482c4  000002c4  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .gnu.version_r 00000020  080482d8  080482d8  000002d8  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  8 .rel.dyn      00000008  080482f8  080482f8  000002f8  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  9 .rel.plt      00000030  08048300  08048300  00000300  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 10 .init         00000023  08048330  08048330  00000330  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 11 .plt          00000070  08048360  08048360  00000360  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 12 .plt.got      00000008  080483d0  080483d0  000003d0  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 13 .text         00000242  080483e0  080483e0  000003e0  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 14 .fini         00000014  08048624  08048624  00000624  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 15 .rodata       00000058  08048638  08048638  00000638  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 16 .eh_frame_hdr 0000002c  08048690  08048690  00000690  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 17 .eh_frame     000000b0  080486bc  080486bc  000006bc  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 18 .init_array   00000004  08049f08  08049f08  00000f08  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 19 .fini_array   00000004  08049f0c  08049f0c  00000f0c  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 20 .jcr          00000004  08049f10  08049f10  00000f10  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 21 .dynamic      000000e8  08049f14  08049f14  00000f14  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 22 .got          00000004  08049ffc  08049ffc  00000ffc  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 23 .got.plt      00000024  0804a000  0804a000  00001000  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 24 .data         00000008  0804a024  0804a024  00001024  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 25 .bss          00000008  0804a02c  0804a02c  0000102c  2**2
                  ALLOC
 26 .comment      0000002d  00000000  00000000  0000102c  2**0
                  CONTENTS, READONLY