Расположение в памяти переменных, определенных в общей библиотеке - PullRequest
5 голосов
/ 03 июня 2019

TL; DR. Почему переменные, определенные в общей библиотеке, находятся в сегментах, определенных в основной программе, а не в общей библиотеке?

Я пытаюсь понять динамическое связывание файла ELF. Я написал фиктивную общую библиотеку

// varlib.so

int x = 42;

void set_x() {
    x = 16;
}

и программа, которая его использует

// main.out

#include <stdlib.h>
#include <stdio.h>

extern int x;
void set_x();

int f() {
    return x;
}

int main(int argc, char** argv) { 
    set_x();
    printf("%d\n", f());
    return 0;
}

прежде чем я посмотрел на сборку, я предполагал, что сегмент, который содержит x, будет взят из varlib.so (вероятно, .data сегмент), а main.out будет использовать его таблицу GOT (и перемещение, чтобы исправить ПОЛУЧИЛ запись в таблице) для доступа x. Однако при осмотре я обнаружил, что

В main.out

Функция f определяется как

0000000000400637 <f>:
  400637:   55                      push   rbp
  400638:   48 89 e5                mov    rbp,rsp
  40063b:   8b 05 f7 09 20 00       mov    eax,DWORD PTR [rip+0x2009f7]        # 601038 <x>
  400641:   5d                      pop    rbp
  400642:   c3                      ret    

с переездом

Relocation section '.rela.dyn' at offset 0x490 contains 3 entries:
    Offset             Info             Type               Symbol's Value  Symbol's Name + Addend
0000000000601038  0000000600000005 R_X86_64_COPY          0000000000601038 x + 0

, где 0x601038 находится в разделе .bss main.out.

В libvar.so

set_x определяется как

00000000000005aa <set_x>:
 5aa:   55                      push   rbp
 5ab:   48 89 e5                mov    rbp,rsp
 5ae:   48 8b 05 23 0a 20 00    mov    rax,QWORD PTR [rip+0x200a23]        # 200fd8 <x-0x48>
 5b5:   c7 00 10 00 00 00       mov    DWORD PTR [rax],0x10
 5bb:   90                      nop
 5bc:   5d                      pop    rbp
 5bd:   c3                      ret    

с переездом

Relocation section '.rela.dyn' at offset 0x3d0 contains 8 entries:
    Offset             Info             Type               Symbol's Value  Symbol's Name + Addend
0000000000200fd8  0000000500000006 R_X86_64_GLOB_DAT      0000000000201020 x + 0

, где 0x200fd8 находится в разделе .got varlib.so.

Таким образом, может показаться, что x фактически находится в сегменте main.out (в частности, в сегменте .bss), и libvar.so должен использовать свою таблицу .got для доступа к ней. то есть полная противоположность тому, что я, хотя! Это кажется странным, поскольку x определяется как extern в main.out и получает значение в varlib.so. Я думаю, что понимаю большинство технических деталей (хотя все еще немного сбит с толку о точных значениях типов перемещения R_X86_64_COPY и R_X86_64_GLOB_DAT; если у кого-то есть хорошее руководство по типам перемещения, которое было бы очень полезно).

Итак, мой главный вопрос: почему я делаю это так, а не так, как я делал изначально, хотя это было сделано с x 'проживанием' в сегменте libvar.so и main.out доступом к нему через GOT (или какой-то другой механизм переселения)?

1 Ответ

5 голосов
/ 03 июня 2019

Так что может показаться, что x фактически находится в сегменте main.out (в частности, в сегменте .bss), и libvar.so должен использовать свою таблицу .got для доступа к ней.то есть полная противоположность тому, что я, хотя!

Да и нет.Оставив на мгновение вопрос о том, какой объект ELF фактически обеспечивает x, мы знаем, что эта переменная определена с ненулевым инициализатором.Если мы видим такую ​​переменную, назначенную разделу .bss объекта ELF, то мы знаем, что происходит что-то странное, потому что этот раздел предназначен для инициализированных по умолчанию данных.Он не занимает места в динамическом объекте, поскольку начальные значения с нулевыми битами фактически не сохраняются.Подробнее об этом чуть позже.

[...] Я думаю, что понимаю большинство технических деталей (хотя все еще немного сбит с толку относительно точных значений R_X86_64_COPY и R_X86_64_GLOB_DATтипы перемещения;

И эти типы перемещения являются ключевыми. R_X86_64_COPY - это тип перемещения для инициализированных внешних переменных, определенных в другом объекте ELF, а R_X86_64_GLOB_DAT - соответствующий тип перемещения для глобально-видимые объекты, начальное значение которых хранится в текущем объекте ELF.

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

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

Если у кого-то есть хорошее руководство по типам перемещения, которое было бы очень полезно).

Запросы на отключениересурсы сайта не по теме для SO, но я уверен, что Google может предоставить несколько.В двух словах, однако, они скажут вам следующее:

  • R_X86_64_COPY идентифицирует объект, чье хранилище обеспечивается текущим объектом ELF, но чье начальное значение необходимо скопироватьот другого объекта, и

  • R_X86_64_GLOB_DAT идентифицирует объект, чье хранение обеспечивается другим объектом ELF, но начальное значение которого обеспечивается этим

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

Это кажетсянечетное как x определяется как extern в main.out и задано значение в varlib.so.

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

...