Почему полоса OSX не может удалить слабые символы?

Пытаясь удалить ненужные вещи из моего связанного исполняемого файла, я обнаружил кое-что странное. Предположим, у нас есть простая и понятная программа на C++:

class Foo {
public:
    template <typename T>
    char* getPtr() {
        static char c;
        return &c;
    }
};

char* bar() {
    Foo i;
    return i.getPtr<int>();
}

int main() {
    bar();
} 

Бинарный файл, создаваемый с помощью clang++ t.cc, имеет следующую динамическую таблицу символов:

$ gobjdump -T ./a.out

./a.out:     file format mach-o-x86-64

DYNAMIC SYMBOL TABLE:
0000000100000f40 g       0f SECT   01 0000 [.text] __Z3barv
0000000100000f60 g       0f SECT   01 0080 [.text] __ZN3Foo6getPtrIiEEPcv
0000000100001020 g       0f SECT   08 0080 [.data] __ZZN3Foo6getPtrIiEEPcvE1c
0000000100000000 g       0f SECT   01 0010 [.text] __mh_execute_header
0000000100000f80 g       0f SECT   01 0000 [.text] _main
0000000000000000 g       01 UND    00 0200 dyld_stub_binder

Учитывая, что это исполняемый файл, а не dylib, я хотел бы удалить все записи, кроме тех, которые содержат неопределенные символы. Теоретически двоичный файл все еще будет работать, поскольку информация о требуемой привязке dyld все еще существует, а точка входа определена в некоторой команде загрузки после заголовка mach-o (поэтому ничего общего с таблицей символов).

Попытка strip дает странный результат:

$ strip ./a.out
$ gobjdump -T ./a.out

./a.out:     file format mach-o-x86-64

DYNAMIC SYMBOL TABLE:
0000000005614542      d  3c OPT    00 0000 radr://5614542
0000000100000f60 g       0f SECT   01 0080 [.text] __ZN3Foo6getPtrIiEEPcv
0000000100001020 g       0f SECT   08 0080 [.data] __ZZN3Foo6getPtrIiEEPcvE1c
0000000100000000 g       0f SECT   01 0010 [.text] __mh_execute_header
0000000000000000 g       01 UND    00 0200 dyld_stub_binder

Последние две записи в любом случае не должны быть удалены, так как они требуются dyld для обработки этого исполняемого файла. При этом мы видим, что _main и __Z3barv уже нет. Но символы из class Foo все еще там. Единственная разница между ними и раздетыми состоит в том, что у первых установлен флаг N_WEAK_DEF (0080). Вот скудная информация от <mach-o/nlist.h> об этом флаге:

 /*
 * The N_WEAK_DEF bit of the n_desc field indicates to the static and dynamic
 * linkers that the symbol definition is weak, allowing a non-weak symbol to
 * also be used which causes the weak definition to be discared.  Currently this
 * is only supported for symbols in coalesed sections.
 */
#define N_WEAK_DEF  0x0080 /* coalesed symbol is a weak definition */

К сожалению, это не объясняет, почему strip игнорирует символы с этим флагом.

Итак, вопрос - как научить strip удалять четные N_WEAK_DEF символы, если пользователь просто не хочет их экспортировать.

P.S. Я просмотрел параметры команды и не нашел ничего полезного (-N также удаляет неопределенные символы, поэтому это не вариант). Объявление этого класса с помощью visibility("hidden") создает головоломку, но, к сожалению, это непросто сделать в реальном проекте.


person Sergio    schedule 30.08.2018    source источник
comment
Почему у вас есть релокации nlist, а не релокации байт-кода? Вы пытаетесь удалить файл объекта (MH_OBJECT)? Обычно исполняемые файлы и общие библиотеки связываются с помощью ld64 с использованием формата байт-кода/сжатого перемещения, который dyld считывает при запуске, вместо перемещений на основе nlist, которые являются более информативными, но не используются в окончательных двоичных файлах, если это явно не запрошено.   -  person Kristina Brooks    schedule 30.08.2018
comment
@KristinaBrooks, что вы имеете в виду под переездом? Есть только динамические таблицы символов. Исполняемый файл содержит команду LC_DYLD_INFO_ONLY с информацией о перебазировании и связывании в обычном сжатом виде, но я не предоставил их, так как они не имеют отношения к моему вопросу.   -  person Sergio    schedule 30.08.2018
comment
В вашем двоичном файле есть поток байтов, который сообщает dyld, как настроить свою позицию (без него все глобальные переменные не будут работать), а также выполняет фактическую компоновку. Ваш двоичный файл обычно перемещается из-за ASLR в качестве меры безопасности.   -  person Kristina Brooks    schedule 31.08.2018


Ответы (2)


Мой первый вывод будет заключаться в том, что это результат ошибки радара 5614542, отсюда и этот странный символ, но он не связан с ним.

Я сделаю несколько предположений и предположу, что вы используете релокации nlist, а не новые релокации на основе байт-кода (вы можете проверить, выполнив команду dyld info load). древняя цепочка инструментов или файл MH_OBJECT для основного исполняемого файла, который не прошел последний этап компоновки. Я не уверен на 100%, так ли это, но в любом случае

Извините за мое вышеприведенное предположение, но первоначальный ответ по-прежнему применим, если вы действительно не хотите отказаться от объединения символов, и в этом случае создайте свое приложение с частной связью, но опять же создание экземпляра шаблона делает символ слабым по очень веской причине, он имеет статический конструктор и неявно созданный шаблон, он предпочитает безопасность, поэтому сохраняет символ. Вы не можете экспортировать его вообще за пределы исполняемого файла, хотя у вас здесь небольшой случай, программы на C++, как правило, используют такие вещи, как boost или библиотеки C++, которые зависят от других библиотек C++, которые все создают цепочки, и в конечном итоге вы получаете несколько определения в общем пространстве имен только из-за семантики C++. В вашем небольшом тестовом случае вы можете обойтись без этого, в более крупном приложении, если вы действительно не знаете, что делаете, и изучаете такие вещи, как деревья зависимостей для dylib, просто позвольте dyld делать свою работу. Я думаю, что мой первоначальный ответ по-прежнему применим по большей части, поскольку он объясняет, почему ваш символ помечен как слабый (ODR - это специфичная для С++ концепция, но разные статические компоновщики обрабатывают ее по-разному):


Для более подробного объяснения - это связано с семантикой C++, а именно с правилом одного определения (ODR), которое является близким, но не таким же понятием, как невозможность дублировать сильные символы в одном и том же пространстве имен ( Я имею в виду пространство имен ссылок, а не пространство имен C++, это очень быстро сбивает с толку).

Если вы хотите знать, почему он помечен как слабый, dyld должен иметь возможность объединить его во время динамической компоновки, поскольку повторное использование этого шаблона создаст его экземпляр снова (вызывая нарушение ODR и, в зависимости от контекста, ошибка времени ссылки), так как это неявный экземпляр, который может потребовать или не потребовать объединения (что неизвестно до статического или даже динамического времени ссылки, если, конечно, вы не определяете его как скрытый, и в этом случае вы должны быть чрезвычайно осторожны, поскольку семантика будет сильно различаться в зависимости от таких факторов, как модульная сборка или нет (я имею в виду «модули» LLVM, а не модули TS для C++).

Если бы он не был слабым, вы бы вызвали нарушение ODR в соответствии с правилами C++, определив его как скрытый в более чем 1 единице перевода (если вы повторно использовали этот шаблон, скажем, в заголовке внутри модуля, вы получили бы повторяющиеся ошибки символов). Вам может сойти с рук нарушение ODR, поскольку оно на самом деле не применяется, но будьте готовы к некоторым неприятным сюрпризам (например, с использованием немодульных сборок, иначе говоря, «каждая единица перевода является модулем»).

Определив его как слабый, dyld может выбрать правильные определения для каждого связанного объекта, будь то общая библиотека или исполняемый файл (и не забывайте об общем кеше) во время времени выполнения и связать/переместить их. соответствующим образом в плоском пространстве имен.

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

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

person Kristina Brooks    schedule 30.08.2018
comment
Спасибо за ответ! К сожалению, файл MH_EXECUTE. И он содержит оба экспорта в массиве LC_DYLD_INFO_ONLY и nlist. Но это не должно быть проблемой, так как любая форма представляет собой один и тот же интерфейс. Я понимаю вашу точку зрения, что компоновщик должен видеть все слабые определения, которые входят в окончательный двоичный файл. Но моя ситуация немного другая. Мой двоичный файл уже связан и загружается, поэтому все возможные объединения должны быть выполнены уже до тех пор, пока я не хочу выставлять что-либо за пределы моего исполняемого файла. Вот почему я удивлен, что полоса отказывается удалять эти ненужные записи со слабым определением. - person Sergio; 30.08.2018
comment
Я пересмотрел свой ответ, но он по-прежнему применим по большей части, объединение является частью обеспечения того, чтобы более сложное приложение с большим деревом зависимостей не заканчивалось повторяющимися символами, а dyld требует этих слабых символов для перепривязки, иначе вы получите ошибки времени выполнения в запуск из-за повторяющихся символов, определенных в нескольких библиотеках. - person Kristina Brooks; 31.08.2018

Благодаря проекту Apple с открытым исходным кодом я наконец нашел ответ. Похоже, strip никогда не удаляет глобальные символы слабой защиты:

    ...
    *
    * In 64-bit applications, we only need to save coalesced
    * symbols that are used as weak definitions.
    */
    ...

(К сожалению, на веб-сайте нет построчной навигации, или, может быть, я недостаточно внимательно смотрел) Это объясняет мой случай, поскольку мой двоичный файл - mach-o-x86-64.

Тем не менее, мотивы такого поведения до сих пор неясны.

EDIT: посмотрите ответ Кристины, чтобы понять, почему strip предпочитает сохранять глобальные слабые определения.

person Sergio    schedule 31.08.2018