Подтвердить что ты не робот

Почему информация о длине функции других общих библиотек в ELF?

Наш проект (С++, Linux, gcc, PowerPC) состоит из нескольких разделяемых библиотек. При выпуске новой версии пакета должны быть изменены только те библиотеки, исходный код которых действительно был затронут. С "изменением" я подразумеваю абсолютную двоичную идентификацию (сравнивается контрольная сумма по файлу. Различная контрольная сумма → другая версия в соответствии с политикой). (Я должен упомянуть, что весь проект всегда строится сразу, независимо от того, изменился ли какой-либо код или нет в библиотеке).

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

Однако был случай, когда в деструктор класса TableManager (в файле TableManager.cpp!) библиотеки libTableManager.so был добавлен простой delete, но все же двоичная/контрольная сумма библиотеки libB.so(который использует класс TableManager).

TableManager.h:

class TableManager 
{
public:
    TableManager();
    ~TableManager();
private:
    int* myPtr;
}

TableManager.cpp:

TableManager::~TableManager()
{
    doSomeCleanup();
    delete myPtr;     // this delete has been added
}

Проверяя libB.so на readelf --all libB.so, глядя на раздел .dynsym, выяснилось, что длина всех функций, даже динамически используемых из других библиотек, хранится в libB! Это выглядит так (длина - 668 в третьем столбце):

527: 00000000 668 FUNC GLOBAL DEFAULT UND _ZN12TableManagerD1Ev

Итак, мои вопросы:

  • Почему длина функции фактически хранится в клиентской библиотеке? Достаточно ли начального адреса?
  • Может ли это быть подавлено каким-то образом при компиляции/связывании libB.so(вид "зачистки" )? Мы действительно хотели бы уменьшить эту степень зависимости...
4b9b3361

Ответ 1

Бинго. На самом деле это своего рода "ошибка" в binutils, которую они обнаружили и зафиксировали в 2008 году. Информация о размере на самом деле не полезна!

Что Саймон Болдуин написал в списке рассылки binutils, точно описывает проблему (подчеркивает мной):

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

У нас есть проблема со старой системой (binutils 2.16). Я сравнил его с версией 2.20 на настольной системе и - voilà - длины общих глобальных символов были 0:

157: 00000000     0 FUNC    GLOBAL DEFAULT  UND _ZN12TableManagerD1Ev
158: 00000000     0 FUNC    GLOBAL DEFAULT  UND [email protected]_3.4 (2)
159: 00000000     0 FUNC    GLOBAL DEFAULT  UND [email protected]_2.0 (6)
160: 00000000     0 FUNC    GLOBAL DEFAULT  UND _ZN4Gpio11setErrorLEDENS_

Итак, я сравнил оба исходных кода binutils, и снова - voilà - есть исправление, которое Алан предложил в списке рассылки:

enter image description here

Возможно, мы просто применим патч и перекомпилируем binutils, так как нам нужно оставаться на старой платформе. Благодарим за терпение.

Ответ 2

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

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

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

Последнее кажется (мне) иметь два довольно очевидных недостатка. Первая - это скорость - открытие всех этих дополнительных файлов, анализ их заголовков и т.д., Просто для того, чтобы получить длину функций, почти наверняка будет медленнее, чем чтение дополнительных четырех байтов для каждой функции из текущего файла. Вторая - удобство: до тех пор, пока вы не вызываете какие-либо функции в файле, вам вообще не нужен этот файл. Если вы читаете длины непосредственно из файла (например, как обычно в Windows с DLL), вам нужно, чтобы этот файл присутствовал в целевой системе, даже если он никогда не использовался.

Изменить: поскольку некоторые люди, по-видимому, пропустили (видимо слишком) тонкое значение "намерения выполнить", позвольте мне полностью понять: я достаточно уверен, что это поле не используется (и никогда не использовалось).

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

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

Это, однако, не меняет того факта, что поле все еще существует, и что ОП не спрашивает, почему он существует. Просто говоря "он не используется", хотя и истинно сам по себе, будет/не отвечает на вопрос, который он задал.