Установка __attribute __ ((used)) в C переменная / константа не имеет никакого эффекта - PullRequest
2 голосов
/ 16 апреля 2020

На ARM G CC (обычный C код), когда я объявляю константу как в

__attribute__((used,section(".rodata.$AppID")))
const uint8_t   ApplicationID[16] = {
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x12, 0x34, 0x00, 0x00
};

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

Разве одного тега "used" не должно быть достаточно? В руководстве G CC (6.32.1 Общие атрибуты переменных) я читал:

used

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

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

Я работаю ARM G CC в комплекте с NXP MCUXpresso 11.1, сообщая подробную версию как

GNU C17 (GNU Tools for Arm Embedded Processors 8-2019-q3-update) version 8.3.1 20190703 (release) [gcc-8-branch revision 273027] (arm-none-eabi)
compiled by GNU C version 5.3.1 20160211, GMP version 6.1.0, MPFR version 3.1.4, MPC version 1.0.3, isl version isl-0.18-GMP

1 Ответ

1 голос
/ 17 апреля 2020

Разве одного «использованного» тега может быть недостаточно?

Недостаточно и не нужно. Это не имеет отношения.

Согласно приведенной вами документации G CC, атрибут used применим к определениям stati c переменных. И в качестве ответа, который теперь удален автором, указано, что ваш ApplicationID не является stati c, поэтому атрибут used не имеет никакого эффекта.

Здесь:

/* app_id_extern.c */

#include <stdint.h>

const uint8_t   ApplicationID[16] = {
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x12, 0x34, 0x00, 0x00
};

у нас ApplicationID определено по умолчанию как extern переменная. Класс хранения по умолчанию для переменной файловой области, такой как ApplicationID, равен extern. Соответственно, компилятор сгенерирует объектный файл, в котором определение ApplicationID предоставляется для связывания, как мы можем видеть:

$ gcc -c app_id_extern.c
$ readelf -s app_id_extern.o

Symbol table '.symtab' contains 10 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     ...
     9: 0000000000000000    16 OBJECT  GLOBAL DEFAULT    4 ApplicationID

В объектном файле ApplicationID является 16-байтовым GLOBAL символ в разделе № 4 (в данном случае это .rodata). Привязка GLOBAL означает, что компоновщик stati c может видеть этот символ.

А здесь:

/* app_id_static.c */

#include <stdint.h>

static const uint8_t   ApplicationID[16] = {
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x12, 0x34, 0x00, 0x00
};

, мы ApplicationID явно определены как переменная static. Соответственно, компилятор сгенерирует объектный файл, в котором определение ApplicationID является , а не , доступным для связывания, как мы также можем видеть:

$ gcc -c app_id_static.c
$ readelf -s app_id_static.o

Symbol table '.symtab' contains 10 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     ...
     6: 0000000000000000    16 OBJECT  LOCAL  DEFAULT    4 ApplicationID
     ...

В этом объектном файле ApplicationID - это 16-байтовый символ LOCAL в разделе .rodata. Привязка LOCAL означает, что компоновщик * stai c не может увидеть этот символ.

Компилятор всегда выдает в объектном файле определение переменной extern, например, ApplicationID в app_id_extern.c, даже если на это определение нет ссылки в объектном файле, поскольку внешнее определение будет доступно на компоновщик, и, следовательно, может ссылаться во время компоновки из других объектных файлов, для всего, что компилятор может знать.

Но если переменная static, то компилятор знает, что ее определение недоступно для связи. Поэтому, если он может определить, что определение не указано в самом объектном файле, он может сделать вывод, что определение является избыточным, и вообще не выдавать его в объектный файл. Вот так:

$ gcc -O1 -c app_id_static.c

На этот раз мы просим компилятор выполнить минимальные оптимизации. И тогда

$ readelf -s app_id_static.o

Symbol table '.symtab' contains 8 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS app_id_static.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    2
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    3
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    6
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    4

не имеющее ссылки определение ApplicationID больше не присутствует в объектном файле вообще . Он был оптимизирован.

Теперь для некоторых необычных приложений мы можем захотеть, чтобы компилятор выдал определение символа в объектном файле, который не ссылается на него, и скрывают его от Stati c компоновщик. Вот где в игру вступает атрибут used:

/* app_id_static_used .c */

#include <stdint.h>

static const uint8_t   ApplicationID[16] __attribute__((used,section(".rodata.$AppID"))) = {
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x12, 0x34, 0x00, 0x00
};

Еще раз мы компилируем с оптимизацией -O1:

$ gcc -O1 -c app_id_static_used.c
$ readelf -s app_id_static_used.o

Symbol table '.symtab' contains 10 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     ...
     6: 0000000000000000    16 OBJECT  LOCAL  DEFAULT    4 ApplicationID
     ...

Но на этот раз, благодаря атрибуту used, LOCAL определение ApplicationID вновь появляется в разделе # 4 (который в этом объектном файле .rodata.$AppID)

Так работает атрибут used. Это влияет на поведение компилятора: оно не влияет на компоновщик.

Мы еще не сделали никакой связи. Давайте сделаем это сейчас.

/* hello_world.c */

#include <stdio.h>

int main(void)
{
    puts("Hello world!")
    return 0;
}

Эта программа не ссылается на ApplicationID, но мы введем app_id_static_used.o для связи независимо от:

$ gcc -O1 -c hello_world.c
$ gcc -o hello hello_world.o app_id_static_used.o -Wl,-gc-sections,-Map=mapfile.txt

В связи, Я попросил удалить неиспользуемые разделы ввода и вывести файл карты (-Wl,-gc-sections,-Map=mapfile.txt)

В файле карты мы находим:

Mapfile.txt

...
Discarded input sections
  ...
  .rodata.$AppID
                0x0000000000000000       0x10 app_id_static_used.o
  ...

Компоновщик отбросил секцию .rodata.$AppID, введенную из app_id_static_used.o, поскольку в программе нет ссылок на символы, определенные в этой секции. С атрибутом used мы заставили компилятор выдать определение этого символа static в app_id_static_used.o. Это не заставляет компоновщика нуждаться в нем или сохранять его в исполняемом файле.

Если мы переключаемся с app_id_static_used.c на:

/* app_id_extern_used.c */

#include <stdint.h>

const uint8_t   ApplicationID[16] __attribute__((used,section(".rodata.$AppID"))) = {
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x12, 0x34, 0x00, 0x00
};

, тогда мы делаем то, что вы сделали , применяя атрибут used к определению extern. Атрибут used не имеет никакого эффекта в этом случае, потому что компилятор обязан выдавать определение extern в любом случае. И компоновщик все еще отбрасывает секцию ввода .rodata.$AppID из Исполняемый файл, если программа не ссылается на что-либо в нем.

Пока что ваш исходный файл идентификатора приложения также может быть:

/* app_id_extern_section.c */

#include <stdint.h>

const uint8_t   ApplicationID[16] __attribute__((section(".rodata.$AppID"))) = {
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x12, 0x34, 0x00, 0x00
};

И что вам тогда нужно сделать, это сообщить компоновщик , для которого вы хотите сохранить определение символа ApplicationID, даже если на него не ссылается ваша программа, и даже если даже неиспользуемые разделы удалены.

Чтобы достичь этого, используйте опцию компоновщика --undefined=ApplicationID. Это заставит компоновщика с самого начала предположить, что компоновщик вашей программы обнаружил неопределенную ссылку на ApplicationID, и заставит компоновщика найти и связать его определение, если таковой имеется в любом входном файле. Таким образом:

$ gcc -O1 -c app_id_extern_section.c
$ gcc -o hello hello_world.o app_id_extern_section.o -Wl,-gc-sections,--undefined=ApplicationID

Теперь программа содержит определение ApplicationID, хотя и не ссылается на него:

$ readelf -s hello | grep ApplicationID
    58: 0000000000002010    16 OBJECT  GLOBAL DEFAULT   18 ApplicationID

Раздел # 18 - это раздел .rodata программы:

$ readelf --sections hello | grep '.rodata'
  [18] .rodata           PROGBITS         0000000000002000  00002000

Наконец, обратите внимание, что секция ввода .rodata.$AppID из app_id_extern_section.o была объединена с секцией вывода .rodata, поскольку скрипт компоновщика по умолчанию для компоновщика задает:

.rodata         : { *(.rodata .rodata.* .gnu.linkonce.r.*) }

т.е. все входные секции, соответствующие .rodata, .rodata.* или .gnu.linkonce.r.*, будут выведены в .rodata. Это означает, что даже:

__attribute__((section(".rodata.$AppID")))

является избыточным. Таким образом, исходный файл app-id может быть просто тем, с которого я начал, app_id_extern.c, а опция связывания --undefined=ApplicationID - это все, что необходимо для сохранения в программе символа без ссылки. Если ваш компоновщик не отличается в этом отношении, вы найдете то же самое.

...