Отображение памяти и расположение переменной разделяемой библиотеки, скомпилированной с -fPIC - PullRequest
2 голосов
/ 26 июня 2019

Я использую коробку 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 сегментов, но есть FOUR отображений памяти, почему есть еще два отображения?

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

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

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

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

1 Ответ

3 голосов
/ 28 июня 2019

Как вы видите, в заголовках программы есть ДВА сегмента 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, ...)). Затем mprotect s область от конца первого PT_LOAD, до конца всего отображения без доступа. И, наконец, это mmap s второй 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 */.

...