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

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

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

Однако был случай, когда простой delete был добавлен в деструктор класса TableManager (в файле TableManager.cpp!) библиотеки libTableManager.so, и все же двоичный файл / контрольная сумма библиотеки 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Посмотрев на .дынсым раздел, оказалось, что длина всех функций, даже динамически используемых из других библиотек, хранятся в libB! Это выглядит так (длина 668 в 3-м столбце):

527: 00000000 668 FUNC GLOBAL DEFAULT UND _ZN12TableManagerD1Ev

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

  1. Почему длина функции на самом деле хранится в клиенте lib? Разве начальный адрес не будет достаточным?
  2. Можно ли это как-то подавить при компиляции / компоновке libB.so (что-то вроде «зачистки»)? Мы действительно хотели бы уменьшить эту степень зависимости …

18

Решение

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

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

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

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

157: 00000000     0 FUNC    GLOBAL DEFAULT  UND _ZN12TableManagerD1Ev
158: 00000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSs6assignERKSs@GLIBCXX_3.4 (2)
159: 00000000     0 FUNC    GLOBAL DEFAULT  UND sleep@GLIBC_2.0 (6)
160: 00000000     0 FUNC    GLOBAL DEFAULT  UND _ZN4Gpio11setErrorLEDENS_

Поэтому я сравнил оба исходных кода binutils, и, вуаля, снова есть исправление, предложенное Аланом в списке рассылки:

введите описание изображения здесь

Может быть, мы просто применим патч и перекомпилируем binutils, так как нам нужно оставаться на более старой платформе. Спасибо тебе за твое терпение.

11

Другие решения

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

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

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

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

Редактировать: Так как некоторые люди, очевидно, пропустили (очевидно, слишком) скрытое значение «предназначено для достижения», позвольте мне быть совершенно ясным: я уверен, что это поле фактически не используется (и никогда не использовалось).

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

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

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

8

По вопросам рекламы [email protected]