Допустим, у меня есть исходный файл dll.c, в котором используются функции dlopen и dlsym для загрузки общей библиотеки под названием F.so во время выполнения.

dll.c имеет ссылку на some_function(), а F.so имеет определение some_function().

И предположим, что изображение ниже - это исполняемый объект prog, который получается

linux> gcc -rdynamic -o prog dll.c -ldl

enter image description here

Поэтому раздел .text содержит ссылку на some_function(), которая должна быть разрешена, когда программа загружает F.so и начинает вызывать some_function()

Мои вопросы:

Q1 - мне кажется, что раздел .text (где принадлежит some_function()) в ОЗУ (исполняемый файл копируется в память) должен быть изменен динамическим компоновщиком, чтобы ссылка на {{X2} } можно решить, правильно ли я понимаю?

Q2 - Если динамическому компоновщику необходимо изменить раздел .text в ОЗУ, как он это сделает? Насколько я понимаю, раздел .text является сегментом только для чтения в ОЗУ, как можно изменить сегмент, доступный только для чтения, если он называется сегментом только для чтения?

0
slowjams 2 Сен 2020 в 18:07

2 ответа

Лучший ответ

Q1 - мне кажется, что раздел .text (где принадлежит some_function ()) в RAM (исполняемый файл копируется в память) должен быть изменен динамическим компоновщиком ...

Нет. Фактический код использует косвенный переход:

.text
    ...
    call *xsome_function
    ...

.data
xsome_function:
    .long some_function

... поэтому инструкция call не перейдет к определенному адресу, а перейдет к некоторому адресу, который хранится в разделе .data. Динамический компоновщик должен заменить адрес в строке .long some_function, а не адрес в инструкции call.

Если вы связываете код, содержащий «прямую» инструкцию call, компоновщик будет делать то, что JCWasmx86 написал в своем ответе:

.text
    ...
    call some_function@PLT
    ...
some_function@PLT:
    jmp *xsome_function

.data
xsome_function:
    .long some_function

И снова динамический компоновщик должен только заменить адрес в разделе .data.

dlopen

Если вы используете dlopen и dlsym для доступа к своей библиотеке, динамический компоновщик даже ничего не заменяет:

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

При вызове функции с использованием указателя функции компилятор всегда создает инструкцию косвенного перехода (например, call %eax).

Q2 - Если динамическому компоновщику необходимо изменить раздел .text в ОЗУ, ...

Несколько лет назад я написал небольшой компилятор / компоновщик для Linux.

Поскольку я пришел из разработчиков Windows, а динамический компоновщик Window фактически заменяет адреса в разделе .text, я написал свой компилятор / компоновщик так же, как и в Windows:

Результат: динамический компоновщик (ld-linux.so) просто аварийно завершил работу при запуске программы, скомпилированной с использованием моего компилятора, что вызвало «ошибку сегментации», потому что динамический компоновщик не может писать в сегмент .text под Linux.

< Сильный > - Edit -

Пример:

Если используются dlopen() и dlsym(), код C не выглядит следующим образом:

extern double sin(double x);
...
sine = sin(angle);

Вместо этого код C выглядит так:

static double (*psin)(double x);
...
sofile = dlopen("libm.so",FLAGS);
psin = dlsym(sofile,"sin");
...
sine = psin(angle);
1
Martin Rosenau 3 Сен 2020 в 05:33

Q1 - мне кажется, что раздел .text (где принадлежит some_function ()) в RAM (исполняемый файл копируется в память) должен быть изменен динамическим компоновщиком, чтобы можно было разрешить ссылку some_function (), это мой понимание правильное?

Так не должно быть. Есть PLT (таблица привязки процедур). По сути, это так:

foo@PLT:
        jmp <someTemporaryAddress>
main:
        call foo@PLT

А затем, во время выполнения, динамический компоновщик исправляет только этот раздел, потому что это проще, чем поиск вызовов во множестве машинного кода.

Q2 - Если динамическому компоновщику необходимо изменить раздел .text в ОЗУ, как он это сделает? Насколько я понимаю, раздел .text - это сегмент только для чтения в ОЗУ, как можно изменить сегмент только для чтения, если он называется только для чтения?

Это делается перед тем, как сделать его исполняемым / доступным только для чтения.

1
JCWasmx86 2 Сен 2020 в 15:12