Отображение памяти и переменное расположение разделяемой библиотеки, скомпилированное с -fPIC

Я использую ящик Linux и хочу выяснить адреса символов внутри разделяемой библиотеки Position-Independent-Code во время выполнения, теперь я могу добиться этого, согласно некоторым наблюдениям, однако у меня все еще есть некоторые вопросы о программе/библиотеке загружается (да, я знаю как, но не знаю почему). Предположим, у нас есть следующие два исходных файла C:

// file: main.c
#include <stdio.h>

extern int global_field;
void main() {
    printf("global field(%p) = %d\n", &global_field, global_field);
}

// file: lib.c
int global_field = 1;

И мы скомпилируем приведенный выше код с помощью следующей команды:

gcc -fPIC -g -c lib.c -o lib.o      # note the -fPIC flag here
gcc -fPIC -g -c main.c -o main.o    # note the -fPIC flag here
gcc -shared -o lib.so lib.o
gcc -o main main.o ./lib.so

И readelf -sW lib.so показывает символ global_field:

Num:    Value          Size Type    Bind   Vis      Ndx Name
  ...
  8: 0000000000201028     4 OBJECT  GLOBAL DEFAULT   21 global_field
  ...

И readelf -lW lib.so выводит следующие заголовки программы:

...
Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x00065c 0x00065c R E 0x200000
  LOAD           0x000df8 0x0000000000200df8 0x0000000000200df8 0x000234 0x000238 RW  0x200000
  DYNAMIC        0x000e18 0x0000000000200e18 0x0000000000200e18 0x0001c0 0x0001c0 RW  0x8
  NOTE           0x000190 0x0000000000000190 0x0000000000000190 0x000024 0x000024 R   0x4
  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10
  GNU_RELRO      0x000df8 0x0000000000200df8 0x0000000000200df8 0x000208 0x000208 R   0x1

И теперь запускаем программу, она выводит следующее:

global field(0x7ffff7dda028) = 1

И cat /proc/<pid>/maps выводит следующее:

...
7ffff7bd9000-7ffff7bda000 r-xp 00000000 fd:02 18650951    /.../lib.so
7ffff7bda000-7ffff7dd9000 ---p 00001000 fd:02 18650951    /.../lib.so
7ffff7dd9000-7ffff7dda000 r--p 00000000 fd:02 18650951    /.../lib.so
7ffff7dda000-7ffff7ddb000 rw-p 00001000 fd:02 18650951    /.../lib.so
...

Извините, здесь слишком много кода... Теперь мои вопросы:

  1. Как видите, в заголовках программ ДВА LOAD сегмента, но есть ЧЕТЫРЕ сопоставления памяти, зачем еще два сопоставления?

  2. Для двух сегментов LOAD, как выяснить, какой сегмент соответствует какой области памяти? Есть ли какой-нибудь стандарт или какое-либо руководство?

  3. значение символа global_field равно 0000000000201028 (см. вывод readelf -sW lib.so), однако согласно стандарту ELF:

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

Я знаю, что это позиционно-независимый код, он НЕ МОЖЕТ быть виртуальным адресом и ДОЛЖЕН быть каким-то смещением. Вычтите адрес global_field со значением символа: 0x7ffff7dda028 - 0x201028 = 0x7ffff7bd9000, кажется, что смещение основано на начальном адресе самого нижнего отображения памяти (см. вывод cat /proc/<pid>/maps). Однако существует ли какой-либо стандарт, говорящий нам, как программно определить тип значения символов (виртуальный адрес или смещение)? И если это смещение, почему смещение должно основываться на этом и почему оно не основывается на своей собственной области памяти (я полагаю, что ее собственная область является последней, поскольку у нее есть разрешение на запись)?


person Kelvin Hu    schedule 26.06.2019    source источник


Ответы (1)


Как видите, в заголовках программ ДВА сегмента LOAD, но ЧЕТЫРЕ отображения памяти, зачем еще два отображения?

Потому что GNU_RELRO указывает динамическому загрузчику сделать первые 0x208 байта второго PT_LOAD сегмента доступными только для чтения.

Если вы свяжете библиотеку с gcc -shared -o lib.so lib.o -Wl,-z,norelro, вы получите только 3 сопоставления... Что по-прежнему оставляет вопрос, почему их 3, а не два?

Вы заметите, что это сопоставление:

7ffff7bda000-7ffff7dd9000 ---p 00001000 fd:02 18650951    /.../lib.so

на самом деле является «дырой» в пространстве процесса (доступ запрещен). Вы также заметите, что выравнивание для второго PT_LOAD (на самом деле для обоих) очень велико: 0x200000.

Это сделано для обеспечения возможности работы со страницами размером 1 МБ.

Если вы снова свяжетесь с gcc -shared -o lib.so lib.o -Wl,-z,norelro,-z,max-page-size=4096, теперь у вас будут только два ожидаемых сопоставления.

Что на самом деле происходит для случая по умолчанию, так это то, что загрузчик должен сохранять смещение между первым и вторым PT_LOAD (иначе двоичный файл не будет работать правильно). Поэтому он создает большое отображение (охватывающее оба сегмента PT_LOAD) по адресу, выбранному ядром (через mmap(0, ...)). Затем mprotects регион от конца первого PT_LOAD до конца всего отображения без доступа. И, наконец, он mmaps создает второй сегмент PT_LOAD по желаемому адресу, используя флаг MAP_FIXED, оставляя дыру между двумя сопоставлениями.

Для двух сегментов LOAD, как выяснить, какой сегмент соответствует какой области памяти? Есть ли какой-нибудь стандарт или какое-либо руководство?

Вы можете сказать довольно легко по смещению. Отображения со смещением 0 соответствуют первому PT_LOAD, отверстие ничему не соответствует, а отображение со смещением 00001000 соответствует второму PT_LOAD.

кажется, что смещение основано на начальном адресе самого нижнего отображения памяти

Правильно: это перемещение для всего изображения lib.so ELF (определяется самым первым mmap(0, ...). Это перемещение применяется к каждому символу в изображении.

Однако существует ли какой-либо стандарт, говорящий нам, как программно определить тип значения символов (виртуальный адрес или смещение)?

Стандарта нет. Но вы можете использовать dladdr, чтобы узнать "базовый адрес "(переезд). В частности, dli_fbase; /* Base address at which shared object is loaded */.

person Employed Russian    schedule 28.06.2019