Подтвердить что ты не робот

Параметры запроса в разделе -ffunction-section и -fdata-sections gcc

Ниже перечислены страницы GCC для разделов функций и разделов разделов данных:

-ffunction-sections
-fdata-sections

Поместите каждую функцию или элемент данных в свой раздел в выходной файл, если цель поддерживает произвольные разделы. Имя функции или имя элемента данных определяет имя раздела в выходном файле. Используйте эти параметры в системах, где компоновщик может выполнять оптимизацию, чтобы улучшить локальность ссылки в пространстве команд. Большинство систем, использующих формат объекта ELF, и процессоры SPARC, работающие под управлением Solaris 2, имеют компоновщики с такими оптимизациями. AIX может иметь такую ​​оптимизацию в будущем.

Используйте эти параметры только тогда, когда есть значительные преимущества от этого. Когда вы укажете эти параметры, ассемблер и компоновщик создадут более крупные объекты и исполняемые файлы, а также будут медленнее. Вы не сможете использовать gprof для всех систем, если вы укажете эту опцию, и у вас может быть проблемы с отладкой, если вы укажете оба параметра и -g.

У меня создалось впечатление, что эти параметры помогут уменьшить размер исполняемого файла. Почему эта страница говорит, что она создаст более крупные исполняемые файлы? Я что-то пропустил?

4b9b3361

Ответ 1

При использовании этих параметров компилятора вы можете добавить опцию компоновщика --gc-sections (может быть неправильное имя), которая удалит весь неиспользуемый код.

Ответ 2

Интересно, что использование -fdata-sections может создавать литеральные пулы ваших функций, и, следовательно, ваши функции сами по себе больше. Я заметил это на ARM в частности, но это, вероятно, будет верно в других местах. Бинарный я тестировал только на четверть процента, но он вырос. Глядя на разборку измененных функций, было ясно, почему.

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

Итак, даже при -Wl,--gc-sections результирующее изображение может быть больше, потому что фактический текст функции больше.

Ниже я добавил минимальный пример

Коду ниже достаточно, чтобы увидеть поведение, о котором я говорю. Пожалуйста, не отбрасывайте декларацию volatile и используйте глобальные переменные, оба из которых сомнительны в реальном коде. Здесь они обеспечивают создание двух разделов данных при использовании -fdata-sections.

static volatile int head;
static volatile int tail;

int queue_empty(void)
{
    return head == tail;
}

Версия GCC, используемая для этого теста:

gcc version 6.1.1 20160526 (Arch Repository)

Во-первых, без разделов -fdata мы получаем следующее.

> arm-none-eabi-gcc -march=armv6-m \
                    -mcpu=cortex-m0 \
                    -mthumb \
                    -Os \
                    -c \
                    -o test.o \
                    test.c

> arm-none-eabi-objdump -dr test.o

00000000 <queue_empty>:
 0: 4b03     ldr   r3, [pc, #12]   ; (10 <queue_empty+0x10>)
 2: 6818     ldr   r0, [r3, #0]
 4: 685b     ldr   r3, [r3, #4]
 6: 1ac0     subs  r0, r0, r3
 8: 4243     negs  r3, r0
 a: 4158     adcs  r0, r3
 c: 4770     bx    lr
 e: 46c0     nop                   ; (mov r8, r8)
10: 00000000 .word 0x00000000
             10: R_ARM_ABS32 .bss

> arm-none-eabi-nm -S test.o

00000000 00000004 b head
00000000 00000014 T queue_empty
00000004 00000004 b tail

Из arm-none-eabi-nm мы видим, что queue_empty имеет длину 20 байтов (14 шестнадцатеричных), а вывод arm-none-eabi-objdump показывает, что в конце функции есть одно перемещающее слово, это адрес раздела BSS ( раздел для неинициализированных данных). Первая команда в функции загружает это значение (адрес BSS) в r3. Следующие две команды загружаются относительно r3, компенсируя соответственно 0 и 4 байта. Эти две нагрузки - это нагрузки значений головы и хвоста. Мы можем видеть эти смещения в первом столбце вывода из arm-none-eabi-nm. nop в конце функции состоит в том, чтобы слова выровнять адрес литерального пула.

Далее мы увидим, что произойдет, когда добавлены разделы -fdata.

arm-none-eabi-gcc -march=armv6-m \
                  -mcpu=cortex-m0 \
                  -mthumb \
                  -Os \
                  -fdata-sections \
                  -c \
                  -o test.o \
                  test.c

arm-none-eabi-objdump -dr test.o

00000000 <queue_empty>:
 0: 4b03     ldr   r3, [pc, #12]    ; (10 <queue_empty+0x10>)
 2: 6818     ldr   r0, [r3, #0]
 4: 4b03     ldr   r3, [pc, #12]    ; (14 <queue_empty+0x14>)
 6: 681b     ldr   r3, [r3, #0]
 8: 1ac0     subs  r0, r0, r3
 a: 4243     negs  r3, r0
 c: 4158     adcs  r0, r3
 e: 4770     bx    lr
    ...
             10: R_ARM_ABS32 .bss.head
             14: R_ARM_ABS32 .bss.tail

arm-none-eabi-nm -S test.o

00000000 00000004 b head
00000000 00000018 T queue_empty
00000000 00000004 b tail

Сразу же мы видим, что длина queue_empty увеличилась на четыре байта до 24 байтов (18 hex) и что теперь есть две перестановки в пуле queue_empty literal. Эти перестановки соответствуют адресам двух разделов BSS, которые были созданы, по одному для каждой глобальной переменной. Здесь должно быть два адреса, потому что компилятор не может знать относительную позицию, в которой компоновщик в конечном итоге помещает эти два раздела. Рассматривая инструкции в начале queue_empty, мы видим, что есть дополнительная загрузка, компилятор должен генерировать отдельные пары нагрузки, чтобы получить адрес раздела, а затем значение переменной в этом разделе. Дополнительная инструкция в этой версии queue_empty не делает тело функции более длинной, она просто занимает пятно, которое ранее было nop, но в общем случае этого не будет.

Ответ 3

Вы можете использовать -ffunction-sections и -fdata-sections в статических библиотеках, что увеличит размер статической библиотеки, так как каждая функция и глобальная переменная данных будут помещены в отдельный раздел.

И затем используйте -Wl,--gc-sections в программе, связанной с этой статической библиотекой, которая удалит неиспользуемые разделы.

Таким образом, окончательный двоичный файл будет меньше, чем без этих флагов.

Будьте осторожны, поскольку -Wl,--gc-sections может сломать вещи.

Ответ 4

Я получаю лучшие результаты, добавляя дополнительный шаг и создавая архив .a:

  • во-первых, gcc и g++ используются с -ffunction-sections -fdata-sections flags
  • тогда все .o объекты помещаются в архив .a с ar rcs file.a *.o
  • наконец, компоновщик вызывается с -Wl,-gc-sections,-u,main опциями
  • для всех, оптимизация установлена ​​на -Os.

Ответ 5

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

Для статического lib с -Wl, -gc-секциями удаление неиспользуемого раздела, скорее всего, сделает больше, чем для небольшого увеличения.