Принуждение определенных переменных, генерируемых компилятором, в определенные секции ELF (с gcc)

Я начну с окончательного вопроса: в C с gcc можно получить значения (s) __func__ (или эквивалентно, __FUNCTION__), хранящиеся в разделе, отличном от .rodata (или где -mrodata= точки) или его подразделение?

Полное объяснение:

Скажем, у меня есть журнал регистрации:

#define LOG(fmt, ...) log_internal(__FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__)

(Оператор конкатенации строк ##, используемый в этом унарном контексте, использует предыдущую запятую, если и только если список __VA_ARGS__ пуст, что позволяет использовать строку формата с аргументами или без них.)

Я могу использовать макрос обычно:

void my_function(void) {
    LOG("foo!");
    LOG("bar: %p", &bar);
}

может печатать (очевидно, в зависимости от реализации log_internal):

foo.c:201(my_function) foo!
foo.c:202(my_function) bar: 0x12345678

В этом случае строки формата ("foo" и "bar: %p") и строки препроцессора ("foo.c" и "my_function") являются анонимными данными только для чтения и автоматически помещаются в раздел .rodata.

Но скажу, что я хочу, чтобы они поехали в другое место (я на встроенной платформе, где почти все работает из ОЗУ для скорости, но ограничения памяти нажимают для перемещения некоторых вещей в ПЗУ). "Легко" перемещать __FILE__ и строку формата:

#define ROM_STR(str) (__extension__({static const __attribute__((__section__(".rom_data"))) char __c[] = (str); (const char *)&__c;}))
#define LOG(fmt, ...) log_internal(ROM_STR(__FILE__), __LINE__, __func__, ROM_STR(fmt), ##__VA_ARGS__)

Вы не можете поместить __attribute__ в анонимную строку, поэтому макрос ROM_STR дает ему временное имя, привязывает его к определенному разделу, а затем оценивает начальный адрес, поэтому он может легко заменить. Это не работает, если вы попытаетесь передать переменную char * в LOG как строку формата, но я готов исключить этот прецедент.

Обычно анонимные строки, которые являются идентичными, объединяются компилятором в одно место хранения, поэтому каждый экземпляр __FILE__ в одном файле будет иметь один и тот же адрес времени выполнения. При явном наименовании в ROM_STR каждый экземпляр получит свое собственное место хранения, поэтому, вероятно, на самом деле не имеет смысла использовать его на __FILE__.

Однако я хотел бы использовать его на __func__. Проблема в том, что __func__ не та же магия, что и __FILE__. Из руководства gcc "Имена функций как строки":

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

static const char __func__[] = "function-name";
Появился

где function-name - имя лексически-охватывающей функции. Это имя является неотъемлемым именем функции.... Эти идентификаторы не являются макросами препроцессора. В GCC 3.3 и ранее, и только в C, __FUNCTION__ и __PRETTY_FUNCTION__ рассматривались как строковые литералы; они могут использоваться для инициализации массивов char, и они могут быть объединены с другими строковыми литералами. GCC 3.4 и более поздние рассматриваются как переменные, например __func__.

Таким образом, если вы завернете __func__ с помощью ROM_STR, вы получите

error: invalid initializer

и если вы попытаетесь поместить атрибут раздела до или после использования __func__, вы получите

error: expected expression before ‘__attribute__’

или

error: expected ‘)’ before ‘__attribute__’

И, таким образом, мы возвращаемся к первому вопросу: возможно ли получить __func__ в разделе по моему выбору? Может быть, я могу использовать -fdata-sections и сделать магию компоновщика script, чтобы получить .rodata.__func__.* исключение из остальной части .rodata? Если да, то какой синтаксис для globbing с исключением в компоновщик script? Другими словами, где-то у вас есть *(.rodata*) - я мог бы поставить *(.rodata.__func__*) где-то в другом месте, но мне нужно будет изменить исходный глобус, чтобы исключить его, поэтому я не получаю две копии.

Ответ 1

Похоже, я ответил на свой вопрос в конце с помощью бизнеса -fdata-sections, я просто не понял GNU Linker, чтобы его увидеть. На самом деле мне не нужно сглаживание с исключением, если я сначала укажу бит *(.rodata.__func__*). Любые разделы, в которых сопоставляются glob, будут отмечены как используемые, поэтому более поздний glob для *(.rodata*) не будет считать их двойным и скопировать их в другое место. Мне не нужно тегировать их с помощью ROM_STR вообще. Круто!

Важно отметить, что -fdata-sections действительно помещает каждую строку функций в свой собственный раздел .rodata.__func__.1234 (я не уверен, какой шаблон соответствует числу). Я не знаю, получают ли анонимные строки свои разделы; если это так, я мог бы использовать те же уловки-линкера, чтобы захватить все анонимные строки вместо макроса атрибута раздела ROM_STR, но, вероятно, это была бы плохая идея. ROM_STR используется в макросе LOG, поэтому он гарантирован только для применения к строкам формата ведения журнала. Если бы я запустил все анонимные строки в ПЗУ с помощью линкера, который включал бы обычные данные сообщений, и я бы заплатил штраф за производительность во время исполнения, чтобы получить доступ к нему со вспышки. Поэтому я не знаю, возможно ли это, но его целесообразность будет зависеть от ваших конкретных требований к системе.

Ответ 2

Один из подходов, который может быть более хакером, чем вам хотелось бы, - это вставить script, чтобы изменить имена разделов между компиляцией и сборкой. Например:

gcc -fdata-sections -S -o test.s test.c
sed 's/^\t.section\t\.rodata\.__func__\.[0-9]*/\t.section .rom_data/' -i test.s
gcc -c test.s

Вы также можете попытаться написать переход преобразования clang, чтобы разместить объявления __func__ в выбранном вами разделе или написать программу обработки объектных файлов, используя libbfd.