Различия между GCC/Clang при линковке

Из-за значительно лучшего времени компиляции C++ я недавно добавил возможность компилировать проект для микроконтроллера ARM Cortex-M4 с помощью Clang вместо инструментальной цепочки arm-none-eabi-gcc. Весь процесс прошел довольно гладко, и у меня быстро появились рабочие файлы ELF и HEX. Только вчера вечером я заметил, что файлы ELF на самом деле довольно сильно отличаются...

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

ELF GCC содержит следующие разделы (помимо отладки)

    Section Headers:
      [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
      [ 0]                   NULL            00000000 000000 000000 00      0   0  0
      [ 1] .vector_table     PROGBITS        08000000 010000 000010 00   A  0   0  4
      [ 2] .version          NOBITS          08000010 010010 000010 00  WA  0   0  1
      [ 3] .text             PROGBITS        08000020 010020 000138 00  AX  0   0  4
      [ 4] .rodata           PROGBITS        08000158 010158 000000 00  WA  0   0  1
      [ 5] .data             PROGBITS        20000000 010158 000000 00  WA  0   0  1
      [ 6] .data2            PROGBITS        10000000 010158 000000 00   W  0   0  1
      [ 7] .bss              NOBITS          20000000 020000 00038c 00  WA  0   0 512
      [ 8] .bss2             PROGBITS        2000038c 010158 000000 00   W  0   0  1
      [ 9] ._user_heap_stack NOBITS          2000038c 020000 000e04 00  WA  0   0  1

Но несмотря на то, что.data и.bss отмечены флагом "A" (alloc), загружаются только следующие

    Program Headers:
      Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
      LOAD           0x010000 0x08000000 0x08000000 0x00158 0x00158 RWE 0x10000
      LOAD           0x000000 0x20000000 0x20000000 0x00000 0x01190 RW  0x10000

     Section to Segment mapping:
      Segment Sections...
       00     .vector_table .version .text 
       01     .bss ._user_heap_stack 

Все идет нормально.

Проблема возникла, когда я попытался создать двоичные файлы из ELF, созданного Clang. Эти файлы были огромного размера (256 МБ), что далеко от того, что я ожидал. Теперь, если вы не знакомы с микроконтроллерами ARM, они обычно содержат флэш-память и оперативную память с очень разными адресами (например, 0x0800'0000 для FLASH и 0x2000'0000 для RAM, как показано выше). Итак, у меня уже были некоторые подозрения относительно того, что происходит... Я проверил свой сценарий компоновщика и поместил директиву NOLOAD в каждый раздел, который идет исключительно в RAM. Задача решена?

Ну не совсем. Фактически мои двоичные файлы стали еще больше.

Давайте посмотрим на ELF Кланга. Меня немного беспокоит то, что Clang, похоже, не удаляет раздел для разматывания (ARM.exidx), хотя я компилирую с -fno-unwind-tables и -gc-section, но хорошо, я могу жить с этими 16B.

    Section Headers:
      [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
      [ 0]                   NULL            00000000 000000 000000 00      0   0  0
      [ 1] .vector_table     PROGBITS        08000000 001000 000010 00   A  0   0  4
      [ 2] .version          PROGBITS        08000010 001010 000010 00   A  0   0  1
      [ 3] .text             PROGBITS        08000020 001020 000334 00  AX  0   0  4
      [ 4] .rodata           PROGBITS        08000354 001354 000000 00  AX  0   0  1
      [ 5] .ARM.exidx        ARM_EXIDX       08000354 001354 000010 00  AL  3   0  4
      [ 6] .preinit_array    PROGBITS        08000364 001364 000000 00   A  0   0  1
      [ 7] .init_array       INIT_ARRAY      08000364 001364 000004 04  WA  0   0  4
      [ 8] .fini_array       FINI_ARRAY      08000368 001368 000004 04  WA  0   0  4
      [ 9] .data             PROGBITS        20000000 002000 000000 00  WA  0   0  1
      [10] .data2            PROGBITS        10000000 002000 000000 00  WA  0   0  1
      [11] .bss              NOBITS          20000000 002000 0001ac 00  WA  0   0 512
      [12] .bss2             PROGBITS        200001ac 002000 000000 00  WA  0   0  1
      [13] ._user_heap_stack PROGBITS        200001ac 002000 000e04 00  WA  0   0  1

Вот где становится интересно, и я понятия не имею, что происходит. Что такое GNU_RELRO и GNU_STACK и как они там попадают? Почему GNU_STACK по адресу 0. Есть ли вероятность, что эта запись раздувает мои двоичные файлы?

    Program Headers:
      Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
      LOAD           0x001000 0x08000000 0x08000000 0x00364 0x00364 R E 0x1000
      LOAD           0x001364 0x08000364 0x08000364 0x00008 0x00008 RW  0x1000
      GNU_RELRO      0x001364 0x08000364 0x08000364 0x00008 0x00c9c R   0x1
      GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0
      EXIDX          0x001354 0x08000354 0x08000354 0x00010 0x00010 R   0x4

     Section to Segment mapping:
      Segment Sections...
       00     .vector_table .version .text .rodata .ARM.exidx 
       01     .preinit_array .init_array .fini_array 
       02     .preinit_array .init_array .fini_array 
       03     
       04     .rodata .ARM.exidx 

Дальнейшие вопросы:

  • Как GCC может удалить все разделы RAM, несмотря на то, что у них ранее не было директивы NOLOAD в сценарии компоновщика?
  • Рабочий размер в ELF Clang считает минимальный размер стека, который я определяю в сценарии компоновщика для раздела.data, тогда как в ELF GCC это не так. Как так? Мой скрипт компоновщика содержит раздел, который выглядит так
    ._user_heap_stack (NOLOAD) :
    {
      . = ALIGN(8);
      PROVIDE ( end = . );
      PROVIDE ( _end = . );
      . = . + _Min_Heap_Size;
      . = . + _Min_Stack_Size;
      . = ALIGN(8);
    } >RAM
  • но, насколько мне известно, это должно только "проверить", что оперативной памяти достаточно, чтобы покрыть мой определенный минимальный размер кучи и стека. Этот раздел на самом деле ничего не содержит, так как его можно засчитать в.data?

Я знаю, что могу удалить ненужные разделы с помощью objcopy при создании двоичных файлов, но мне действительно хотелось бы понять эти тонкие различия между GCC и Clang.

/edit Я только что заметил, что мой раздел._user_heap_stack имеет разные типы в зависимости от компилятора (NOBITS vs PROGBITS). Думаю, это объясняет, почему он засчитывается в.data...

/edit Теперь (потенциальная) ошибка в @ LLVMhttps://bugs.llvm.org/show_bug.cgi?id=46299

/edit И закрыт с lld 10.0.1https://bugs.llvm.org/show_bug.cgi?id=46225

0 ответов

Другие вопросы по тегам