Процесс уменьшения размера исполняемого файла

Я создаю шестнадцатеричный файл для запуска на ARM-процессоре, который я хочу поддерживать ниже 32K. В настоящее время это намного больше, и я задавался вопросом, может ли кто-нибудь посоветовать, какой лучший подход к его уменьшению?

Вот что я сделал до сих пор

  1. Таким образом, я запустил "размер" на нем, чтобы определить, насколько величен шестнадцатеричный файл.
  2. Затем "размер" снова, чтобы увидеть, насколько большой каждый из объектных файлов является этой ссылкой для создания шестнадцатеричных файлов. Похоже, что большая часть размера поступает из внешних библиотек.
  3. Затем я использовал "readelf", чтобы увидеть, какие функции занимают большую часть памяти.
  4. Я просмотрел код, чтобы узнать, могу ли я устранить вызовы этих функций.

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

Итак, каковы следующие шаги?

Ответ на ответы:

  • Как я вижу, существуют функции, которые занимают много памяти. Однако я не могу найти то, что называется.
  • Я хочу опустить эти функции (если возможно), но я не могу найти, что их зовут! Может быть вызвано из любого количества функций библиотеки, которые я предполагаю.
  • Я думаю, компоновщик работает по своему желанию, он включает только соответствующие файлы библиотеки. Откуда вы знаете, включены ли только соответствующие функции? Можете ли вы установить флаг или что-то в этом роде?
  • Я использую GCC

Ответ 1

Общий список:

  • Убедитесь, что у вас отключены параметры отладки компилятора и компоновщика.
  • Скомпилировать и связать все параметры размера (-Os в gcc)
  • Запустите strip в исполняемом файле
  • Создайте файл карты и проверьте размеры своих функций. Вы можете либо получить ваш компоновщик для создания файла карты (-M при использовании ld), либо вы можете использовать objdump для окончательного исполняемого файла (обратите внимание, что это будет работать только с незастроенным исполняемым файлом!) Это не решит проблему, но это даст вам знать о худших преступниках.
  • Используйте nm для исследования символов, вызываемых из каждого из ваших объектных файлов. Это должно помочь найти, кто вызывает функции, которые вы не хотите вызывать.

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

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

Нет причин, по которым вы не можете сделать тот же трюк. Конечно, вы можете утверждать, что если функции не называются, вы можете, вероятно, удалить их самостоятельно.

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

Ответ 2

Другая оптимизация, которая может сэкономить вам работу, - это разделы -функции, -Wl, -gc-sections, если вы используете GCC. Хорошей инструментальной цепочки не нужно будет говорить об этом.

Объяснение: разделы ссылок GNU ld, а GCC испускает один раздел на единицу перевода, если вы не указали иначе. Но в С++ узлы в графе dependecy являются объектами и функциями.

Ответ 3

Просто для проверки и документирования в будущем, но вы используете инструкции Thumb? Это 16-разрядные версии обычных инструкций. Иногда вам может понадобиться 2 16-битных инструкций, поэтому он не будет экономить 50% в кодовом пространстве.

Достойный линкер должен выполнять только необходимые функции. Однако вам могут потребоваться настройки компилятора и ссылки для пакетных функций для индивидуальной компоновки.

Ответ 4

В глубоко внедренных проектах я всегда стараюсь избегать использования стандартных функций библиотеки. Даже простые функции, такие как "strtol()", взорвали двоичный размер. Если возможно, просто избегайте этих вызовов.

В большинстве глубоко внедренных проектов вам не требуется универсальное "printf()" или распределение динамической памяти (многие контроллеры имеют 32 КБ или меньше ОЗУ).

Вместо использования "printf()" я использую очень простой пользовательский "printf()" , эта функция может печатать только числа в шестнадцатеричном или десятичном формате не более. Большинство структур данных предварительно распределены во время компиляции.

Ответ 5

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

Должен быть лучший способ сделать это, хотя.

Ответ 6

У Andrew EdgeCombe отличный список, но если вы действительно хотите очистить каждый последний байт, sstrip - хороший инструмент, который отсутствует в список и может сбрить еще несколько kB.

Например, при запуске на strip, он может сбрить ~ 2kB.

Из старого README (см. комментарии вверху этот косвенный исходный файл):

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

Большинство исполняемых файлов ELF построены как с таблицей заголовка программы, так и с таблицу заголовков разделов. Тем не менее, только первый требуется для того, чтобы чтобы ОС загружала, связывала и выполняла программу. sstrip пытается извлеките заголовок ELF, таблицу заголовка программы и ее содержимое, оставляя все остальное в ведро бит. Он может удалять только части файл, который происходит в конце, после того, как детали будут сохранены. Однако, это почти всегда включает таблицу заголовков разделов, а иногда несколько случайных разделов, которые не используются при запуске программы.

Обратите внимание, что из-за некоторой информации, которую он удаляет, исполняемый файл sstrip'd по слухам, имеет проблемы с некоторыми инструментами. Это больше обсуждается в комментариях источника.

Также... для развлекательных/сумасшедших читайте, как сделать наименьший возможный исполняемый файл, этой статьи стоит прочитать.

Ответ 7

Чтобы ответить на эту конкретную потребность:

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

Если вы хотите проанализировать свою базу кода, чтобы узнать, кто называет то, что называется вызываемой функцией, и что-то вроде этого, есть отличный инструмент, который называется "Понимать C", предоставляемый SciTools.

https://scitools.com/

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

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