LINUX.ORG.RU

Линкеры. Оптимизация и удаление лишнего кода во время линковки.

 , , , ,


3

3

Здравствуйте. Собственно у меня несколько вопросов по поводу работы линкеров в современных тулчейнах.
Во первых, с удивлением обнаружил, что линкер из binutils по умолчанию линкует сожержмое объектных файлов ПОЛНОСТЬЮ. То есть, даже если из одного объектного файла с несколькими функциями во всей программе используется только одна из них - всё равно прилинковано будет всё содержимое объектного файла. Что интересно, линкер от M$ под оффтопиком по умолчанию делает тоже самое.
Я знаю что для gcc и ld можно укзать специальные опции, благодяря которым компилятор каждую функцию поместит в отдельную секцию, а линкер потом вырежит все неиспользуемые. У линкера от M$ тоже есть специальная опция.


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


Соответсвенно ещё вопрос, насколько современные линкеры стрипят код - сколько в создаваемых с помощью современных тулчейнов лишнего, совершенно не нужного кода?
Буду особенно благодарен, если кто скажет чего можно почитать в тему.


Забанься, кармадрочер

anonymous ()

Но почему такое поведение не принято по умолчанию?

Потому что может сломать существующий код.

зачем мне в программе код, который нигде не используется?

Есть --as-needed, пользуйся для своих программ им.

где я не прав

Всё просто. Сборка большого количества программ в полуавтоматическом режиме (source-based дистрибутивы) не даёт опыта разработки программ.

Если всё линковать только по надобности, нужно искать правильную последовательность, чтобы линкер не выбросил нужные зависимости. Допустим, у тебя есть lib_a.a и lib_b.a, каждая из которых зависит от другой. Как бы ты их не выстроил, линкер будет выбрасывать ненужные по его мнению куски из первой и обламываться на линковании второй библиотеки.

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

Спасибо за некоторые разъяснения.

Флаг

--as-needed
переданный линкеру
gcc -Wl,--as-needed
не помог. Как код лишней функции линковался, так и линкуется. ЧЯДНТ?

Могу описать как проверяю - но там всё просто.

Как я понял из вашего ответа, линкер, когда просматривает переданные через аргументы *.a и *.o никогда не оглядывается назад - линкует объектные файлы по мере поступления, не проверяя по мере поступления новых объектных файлов нужно ли всё содержимое внутри старых. Именно поэтому порядок, в котором названия библиотек переданы линкеру бывает важен. Так?

arbv ()
Ответ на: комментарий от arbv

как я понимаю нужно вынести функции и данные в секции а потом убрать секции. попробуй

-ffunction-sections -fdata-sections -Wl,--gc-sections

может есть еще что-то?

quest ★★★★ ()
Последнее исправление: quest (всего исправлений: 2)

Соответсвенно ещё вопрос, насколько современные линкеры стрипят код - сколько в создаваемых с помощью современных тулчейнов лишнего, совершенно не нужного кода?

alf@xxx-dev:~$ ll local/bin/cuneiform
-rwxr-xr-x 1 alf user 78005 Апр 20  2012 local/bin/cuneiform
alf@xxx-dev:~$file local/bin/cuneiform
not stripped
alf@xxx-dev:~$ strip local/bin/cuneiform
alf@xxx-dev:~$ ll local/bin/cuneiform
-rwxr-xr-x 1 alf user 20424 Янв 30 13:23 local/bin/cuneiform
alf@xxx-dev:~$ file local/bin/cuneiform
stripped
vtVitus ★★★★★ ()

Но почему такое поведение не принято по умолчанию?

Собственно, ведь и GCC по умолчанию не выполняет никаких оптимизаций (как -O0).

Оптимизации иногда портят программы, бывает, из-за багов в компиляторе, бывает, из-за проблем в самой программе. А уж оптимизации на уровне линкера в binutils появились, ЕМНИП, недавно. Я даже не знал, что ld их поддерживает, думал, они только в gold (который и сам из бета-теста вроде как ещё не вышел).

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

Поэтому в программе, которая а)Имеет чётко известную задачу со времён UNIX б)Предназначена для разработчиков, которые должны знать, как их инструмент работает не стоит по умолчанию применять методы, которые а)Приводят к результатам, которые не так просто объяснить б)Могут приводить к ошибкам.

proud_anon ★★★★★ ()
Последнее исправление: proud_anon (всего исправлений: 1)
Ответ на: комментарий от quest

Да,я знаю об этих опциях, они, разумеется работают. Я написал об этом в самом начале:

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

.

Вопрос не в том как решить эту проблему в одном конкретном случае. Вопрос в том почему для того чтобы удалить из программ явный мусор приходиться сильно сильно просить об этом линкер и компилятор. Не понятно почему линкеры не делают это самомтоятельно и по умолчанию? Почему они работают очень примитивно?

К слову, литературы по линкерам раз ... и обчёлся: http://www.iecc.com/linker/

arbv ()
Ответ на: комментарий от arbv

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

proud_anon тебе ответил - потому что по умолчанию все выключено

quest ★★★★ ()
Ответ на: комментарий от vtVitus
strip local/bin/cuneiform

Разумеется я знаю о команде strip :)

arbv ()
Ответ на: комментарий от proud_anon

думал, они только в gold (который и сам из бета-теста вроде как ещё не вышел).

Поскольку мне известно, он теперь линкер по умолчанию уже год или два.

вполне опеделённую структуру программы и не ожидать, что линкер его перелопатит

У далить явно не нужное не значит перелопатить. Всё равно не понятно.

arbv ()
Ответ на: комментарий от arbv

Как код лишней функции линковался, так и линкуется. ЧЯДНТ?

Вынеси функции в отдельные файлы (единицы компиляции).

Именно поэтому порядок, в котором названия библиотек переданы линкеру бывает важен. Так?

Да.

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

Вынеси функции в отдельные файлы (единицы компиляции).

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

Проблема не возникала бы если линкер:

  • сначала просмаривал бы все объектные файлы;
  • формировал вовремя осмотра таблицу всех функций (вообще говоря символов - symbols) в программе;
  • в таблице бы сохранялось солько раз встетилась функция (symbol) в программе и из какого объектного файла.
  • во время линковки выбросить все функции, которые использовались менее одного раза.

Но это потребует больше памяти для хранения таблицы символов - но это не проблема даже для не топовых машин. Не вижу проблем с этой схемой - ничего не должно сломаться. Что я упустил?

arbv ()
Ответ на: комментарий от devl547

Да. ld из binutils умеет вещи и покруче того, о чём мы говорим. Он умеет использовать intermediate code сгенерированный gcc для того чтобы сильно перелопатить код во время линоковки - там он и лишнее выбросит и заменит конвенции вызова процедур на более быстрые во многих местах и т. п. Но Link Time optimization будет работать только тогда, когда версия линкера и компилятора согласованы - intermediate code на то и intermediate code, что может меняться от версии к версии. Если тут проблемы, буддет использована «классическая» процедура сборки. У gcc с этим получше чем у MSVC кстати.

arbv ()
Ответ на: комментарий от arbv

Но Link Time optimization будет работать только тогда, когда версия линкера и компилятора согласованы

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

devl547 ★★★★★ ()
Ответ на: комментарий от quest

gcc это давно уже не GNU C compiller, а GNU compiler collection

Спвсибо, но я знаю. Не совсем в криокамере. Но тем не менее binutils это не часть GNU Compiler Collection, а отдельный проект.

ждем твои патчи в линкере

Я прошу разъяснить мне некоторые моменты, ели я заблуждаюсь. Не нужно от меня чего либо ждать. Я прошу объяснить есть ли какие либо технические причины для такого поведения. То что такое поведение сложылось исторически и так понятно.

arbv ()
Ответ на: комментарий от devl547

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

Тулчейны то одинаковые - версии не согласованные. К примеру к вам попала библиотека сторонняя и собрана другой (более старой или более новой версией компилятора, где чуть разная версия intermediate code). Всё, lto не получиться - откатимся к классической процедуре сборки. Если компилять всё приложение целиком - нет проблем.

arbv ()
Ответ на: комментарий от arbv

К примеру к вам попала библиотека сторонняя и собрана другой (более старой или более новой версией компилятора, где чуть разная версия intermediate code).

А откуда в библиотеке внутренний промежуточный код компилятора?
Ему просто неоткуда взяться.

devl547 ★★★★★ ()
Ответ на: комментарий от quest

Спасибо за ссылку на грядущие изменения. Особенно понравилось:

A new function reordering pass (controlled by -freorder-functions) significantly reduces startup time of large applications. Until binutils support is completed, it is effective only with link-time optimization.

arbv ()
Ответ на: комментарий от devl547

Есть возможность указать gcc сохоанять в объектные файлы помимо машинного кода и промежуточный (GIMPLE). Для этого есть опция gcc

-flto

Как я понял работает только если линкер gold.
Подробнее тут:
[url]http://gcc.gnu.org/wiki/LinkTimeOptimization[/url]

P.S.
Да, gold пока не линкер по умолчанию. Извиняюсь. Но он доступен как 
ld.gold

.

arbv ()
Ответ на: комментарий от arbv

controlled by -freorder-functions

входит в -O2 так что ожидается ускорение у всех программ

quest ★★★★ ()
Ответ на: комментарий от arbv

Есть возможность указать gcc сохоанять в объектные файлы помимо машинного кода и промежуточный (GIMPLE).

выходит что-то медленно но идет по пути llvm

quest ★★★★ ()
Ответ на: комментарий от i-rinat

Лучше на «ты».

Хорошо.

Объясни, какую проблему ты решаешь.

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

arbv ()
Ответ на: комментарий от quest

выходит что-то медленно но идет по пути llvm

Конкретно в этой фиче [LTO] нет ничего плохого, тем более обратная совместимость остаётся. У линкера в плане оптимизации есть огромный плюс - он способен оптимизировать всю программу целиком. А комрилятор - оптимизирует только на уровне единиц компиляции.

arbv ()
Ответ на: комментарий от arbv

у llvm архитектура лучше - нужно к ней стремится, надеюсь мы это скоро увидим

quest ★★★★ ()
Ответ на: комментарий от arbv

Поскольку мне известно, он теперь линкер по умолчанию уже год или два.

Ну не знаю, во всех дистрах, которые я видел, по умолчанию ld.bfd, а собирать binutils надо с эксплицитным --enable-gold, чтобы он включился.

Да забыл сказать то, что мы обсуждаем в принципе не LTO - это просто стриппинг кода.

Разве для произвольного файла ELF без дополнительной информации можно сказать, какие куски кода в нём используются, а какие - нет?

proud_anon ★★★★★ ()
Последнее исправление: proud_anon (всего исправлений: 2)
Ответ на: комментарий от proud_anon

Ну не знаю, во всех дистрах, которые я видел, по умолчанию ld.bfd, а собирать binutils надо с эксплицитным --enable-gold, чтобы он включился.

Да, я выше отписался что ошибся.

Разве для произвольного файла ELF без дополнительной информации можно сказать, какие куски кода в нём используются, а какие - нет?

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

objdump -t [i]file[/i].o
Исполняемый файл с отладочной информацией тоже эту информацию содержит - то есть для такого файла мы можем сказать какие куски используются, а какие нет. objdump их тоже покажет.

arbv ()
Ответ на: комментарий от unsigned

Символы - это ещё не функции. В объектнике есть информация о расположении функций, «от и до»? По-моему, нет.

А по моему - да. Вот часть выхлопа для объектного файла модержащего функции say_hello() и say_world():

 $ objdump -t file.o
 .....
0000000000000000 l    d  .comment	0000000000000000 .comment
0000000000000000 g     F .text	000000000000000a say_hello
0000000000000000         *UND*	0000000000000000 puts
000000000000000a g     F .text	000000000000000a say_world

arbv ()
Ответ на: комментарий от arbv

Вот содержимое функций после дизассемблирования:

 $ objdump -t file.o

Disassembly of section .text:

0000000000000000 <say_hello>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   bf 00 00 00 00          mov    $0x0,%edi
   9:   e8 00 00 00 00          callq  e <say_hello+0xe>
   e:   5d                      pop    %rbp
   f:   c3                      retq   

0000000000000010 <say_world>:
  10:   55                      push   %rbp
  11:   48 89 e5                mov    %rsp,%rbp
  14:   bf 00 00 00 00          mov    $0x0,%edi
  19:   e8 00 00 00 00          callq  1e <say_world+0xe>
  1e:   5d                      pop    %rbp
  1f:   c3                      retq   

arbv ()
Ответ на: комментарий от quest

strip --strip-unneeded --remove-section=.comment

скромно, в генте так

........
*GNU*) # sys-devel/binutils
        # We'll leave out -R .note for now until we can check out the relevance
        # of the section when it has the ALLOC flag set on it ...
        SAFE_STRIP_FLAGS="--strip-unneeded"
        DEF_STRIP_FLAGS="-R .comment -R .GCC.command.line -R .note.gnu.gold-version"
        SPLIT_STRIP_FLAGS=
        ;;
esac
: ${PORTAGE_STRIP_FLAGS=${SAFE_STRIP_FLAGS} ${DEF_STRIP_FLAGS}}
.......

megabaks ★★★★ ()
Ответ на: комментарий от arbv

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

unsigned ★★★ ()
Ответ на: комментарий от arbv

Имхо, она просто расставила символы по их адресам, для нее это не функции.

Я, например, видел длинные цепочки nop в конце функций в дизассемблере, так что считать функцией код между между соседними символами не совсем правильно. Хотя, наверно, возможно, с поправками.

unsigned ★★★ ()
Последнее исправление: unsigned (всего исправлений: 1)
Ответ на: комментарий от unsigned

Да, вот сейчас дезассемблировал всю программу целиком. Да и в правду есть nop в конце. Но мне кажется, что это связано с выравниванием кода. Если я всё правильно понял - выбросить функцию можно и с помощью nop. Вот часть листинга (функция say_world «лишняя»):

...
0000000000400520 <say_hello>:
  400520:       bf ec 05 40 00          mov    $0x4005ec,%edi
  400525:       e9 b6 fe ff ff          jmpq   4003e0 <puts@plt>
  40052a:       66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)

0000000000400530 <say_world>:
  400530:       bf f3 05 40 00          mov    $0x4005f3,%edi
  400535:       e9 a6 fe ff ff          jmpq   4003e0 <puts@plt>
  40053a:       90                      nop
  40053b:       90                      nop
  40053c:       90                      nop
  40053d:       90                      nop
  40053e:       90                      nop
  40053f:       90                      nop

arbv ()
Ответ на: комментарий от arbv

выбросить функцию можно и с помощью nop.

Запарился. Имел в виду «выбросить функцию можно и вместе с nop.»

arbv ()
Ответ на: комментарий от arbv

Хорошо, а определить, какие вызываются, можно? В таких простых случаях, как у тебя в примерах выше, можно, а если программа, например, вычисляет адрес функции? Функция будет, а прямого call <адрес> - нет

proud_anon ★★★★★ ()
Ответ на: комментарий от proud_anon

Да, это действительно проблематичней. Но такую ситуацию где бы мы просто вычисляли адрес функции, при этом ни разу не обратившись к ней в коде на прямую, очень тяжело. Я не имею в виду, именно вызывали. Например, у нас есть куча функций, которые мы не вызываем на прямую из кода по имени. И у нас есть массив указателей на функции. И, допустим, мы вызывали функции с помощью непрямого вызова (как вы и говорили), узнавая вдрес функции обращаясь по смещению в этом массиве. (У нас, например, какая нибудь доморощенная ВМ для выполнения своего байт-кода). НО!. Для сначала мы должны были заполнить этот массив указателей на функции. А для этого нам нужно было обратиться хотя бы раз к адресу функции на прямую. А нам достаточно обратится к символу напрямую хотя бы раз, чтобы его оставить. Всё нормально пока. P.S. Не совсем в тему, но вспомнилось: http://fgiesen.wordpress.com/2012/04/08/metaprogramming-for-madmen/

arbv ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.