Процесс уменьшения размера исполняемого файла
Я создаю шестнадцатеричный файл для запуска на процессоре ARM, который я хочу оставить ниже 32K. В настоящее время он намного больше этого, и я подумал, есть ли у кого-нибудь совет, как лучше всего его уменьшить?
Вот что я сделал до сих пор
- Поэтому я запустил "size" для определения размера шестнадцатеричного файла.
- Затем снова введите "size", чтобы увидеть, насколько велики все эти объектные файлы для создания шестнадцатеричных файлов. Кажется, что большая часть размера поступает из внешних библиотек.
- Затем я использовал readelf, чтобы увидеть, какие функции занимают больше всего памяти.
- Я искал код, чтобы посмотреть, смогу ли я устранить вызовы этих функций.
Вот где я застрял, есть некоторые функции, которые я не вызываю напрямую (например, _vfprintf), и я не могу найти то, что вызывает его, поэтому я могу удалить вызов (так как я думаю, что он мне не нужен).
Итак, каковы следующие шаги?
Ответ на ответы:
- Как я вижу, есть вызываемые функции, которые занимают много памяти. Однако я не могу найти то, что зовет это.
- Я хочу опустить эти функции (если это возможно), но я не могу найти то, что их вызывает! Может быть вызван из любого числа библиотечных функций.
- Линкер работает, как хотелось бы, я думаю, он включает только соответствующие файлы библиотеки. Как узнать, включены ли только соответствующие функции? Вы можете установить флаг или что-то для этого?
- Я использую GCC
8 ответов
Общий список:
- Убедитесь, что у вас отключены параметры отладки компилятора и компоновщика
- Компилировать и связывать со всеми включенными параметрами размера (-Os в gcc)
- Бежать
strip
на исполняемом файле - Создайте файл карты и проверьте размеры ваших функций. Вы можете получить компоновщик для создания файла карты (
-M
при использовании ld), или вы можете использовать objdump в конечном исполняемом файле (обратите внимание, что это будет работать только на незафиксированном исполняемом файле!) Это на самом деле не решит проблему, но сообщит вам о худших нарушителях. - использование
nm
исследовать символы, которые вызываются из каждого из ваших объектных файлов. Это должно помочь найти тех, кто вызывает функции, которые вы не хотите вызывать.
В первоначальном вопросе был подвопрос о включении только соответствующих функций. gcc
будет включать все функции в каждом объектном файле, который используется. Иными словами, если у вас есть объектный файл, который содержит 10 функций, все 10 функций включены в ваш исполняемый файл, даже если фактически вызывается одна 1.
Стандартные библиотеки (например, libc) будут разбивать функции на множество отдельных объектных файлов, которые затем архивируются. Затем исполняемый файл связывается с архивом. Разбивая на множество объектных файлов, компоновщик может включать только те функции, которые фактически вызываются. (это предполагает, что вы статически связываете)
Нет причины, по которой вы не можете сделать то же самое. Конечно, вы можете утверждать, что если функции не вызываются, вы можете удалить их самостоятельно.
Если вы статически связываетесь с другими библиотеками, вы также можете запускать над ними перечисленные выше инструменты, чтобы убедиться, что они следуют аналогичным правилам.
Другая оптимизация, которая может спасти вашу работу, это -ffunction-section, -Wl,- gc-section, если вы используете GCC. Хороший набор инструментов не нужно будет говорить об этом, хотя.
Объяснение: GNU ld связывает разделы, а GCC выдает один раздел на единицу перевода, если вы не укажете иначе. Но в C++ узлами в графе зависимостей являются объекты и функции.
В глубоко встроенных проектах я всегда стараюсь избегать использования каких-либо стандартных библиотечных функций. Даже простые функции, такие как strtol(), увеличивают размер двоичного файла. Если возможно, просто избегайте этих звонков.
В большинстве глубоко встроенных проектов вам не требуется универсальное "printf()" или динамическое выделение памяти (многие контроллеры имеют 32 КБ или меньше ОЗУ).
Вместо того, чтобы просто использовать "printf()", я использую очень простой пользовательский "printf()", эта функция может печатать только числа в шестнадцатеричном или десятичном формате, не более. Большинство структур данных предварительно выделяются во время компиляции.
У Andrew EdgeCombe отличный список, но если вы действительно хотите очистить каждый последний байт, sstrip - это хороший инструмент, который отсутствует в списке и может сократить на несколько килобайт.
Например, при запуске на strip
Сам он может сбрить ~2 КБ.
Из старого файла README (см. Комментарии вверху этого косвенного исходного файла):
sstrip - это небольшая утилита, которая удаляет содержимое в конце файла ELF, который не является частью образа памяти программы.
Большинство исполняемых файлов ELF построены как с таблицей заголовков программы, так и с таблицей заголовков разделов. Однако для загрузки, связывания и выполнения программы ОС требуется только первое. sstrip пытается извлечь заголовок ELF, таблицу заголовков программы и ее содержимое, оставляя все остальное в сегменте битов. Он может удалить только те части файла, которые появляются в конце, после частей, которые будут сохранены. Однако это почти всегда включает таблицу заголовков разделов и иногда несколько случайных разделов, которые не используются при запуске программы.
Обратите внимание, что из-за некоторой информации, которую он удаляет, исполняемый файл sstrip, по слухам, имеет проблемы с некоторыми инструментами. Об этом подробнее говорится в комментариях источника.
Также... для занимательного / сумасшедшего чтения о том, как сделать минимально возможный исполняемый файл, эту статью стоит прочитать.
Просто чтобы перепроверить и документировать для дальнейшего использования, но используете ли вы инструкции Thumb? Это 16-битные версии нормальных инструкций. Иногда вам могут понадобиться 2 16-битные инструкции, так что это не сэкономит 50% пространства кода.
Приличный компоновщик должен взять только необходимые функции. Однако, вам могут понадобиться настройки компилятора и линке для упаковки функций для отдельных ссылок.
Итак, в конце я просто уменьшил проект до его самой простой формы, затем медленно добавлял файлы один за другим, пока функция, которую я хотел удалить, не появилась в файле 'readelf'. Затем, когда у меня был файл, я закомментировал все и медленно добавил его обратно, пока функция снова не всплыла. Итак, в конце концов, я узнал, как это называется, и удалил все эти звонки... Теперь все работает как надо... сладко!
Хотя это должен быть лучший способ сделать это.
Чтобы ответить на эту конкретную потребность:
• Я хочу опустить эти функции (если это возможно), но я не могу найти то, что их вызывает!! Может быть вызван из любого числа библиотечных функций.
Если вы хотите проанализировать свою кодовую базу, чтобы увидеть, кто что вызывает, кем вызывается данная функция и тому подобное, есть отличный инструмент под названием "Understand C", предоставляемый SciTools.
В прошлом я очень часто использовал его для статического анализа кода. Это действительно может помочь определить дерево зависимостей библиотеки. Это позволяет легко перемещаться вверх и вниз по дереву вызовов среди прочего.
Они предоставляют ограниченную оценку времени, затем вы должны приобрести лицензию.