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

Встроенные статические данные приводят к конфликту типа раздела

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

const int* get_data()
{
  __attribute__((section(".custom")))
  static const int data = 123;

  return & data;
}

inline const int* inline_get_data()
{
  __attribute__((section(".custom")))
  static const int inline_data = 123;

  return & inline_data;
}

int main()
{
  (void) get_data();
  (void) inline_get_data();
  return 0;
}

Значение data и inline_data появится в разделе .custom. Clang компилирует этот пример и дает правильный результат, как это делает MSVC, когда __attributes__ заменяется на соответствующие прагмы.

К сожалению, GCC 5.2 дает следующую ошибку:

error: inline_data causes a section type conflict with data

Проблема сводится к тому, что две переменные имеют разную связь (data находится в разделе с отметкой с a, раздел inline_data отмечен aG). GCC 4.9 не работает таким же образом, если вторая функция не помечена как встроенная, а является шаблоном (GCC 5.2 компилирует это).

GCC 5.2 также компилируется отлично, если одно имя раздела временно изменено и исправлено вручную в сгенерированной сборке.

Существует ли какое-либо известное обходное решение для этой проблемы? Я не контролирую подпись функции, переменные *data создаются макросом, предоставленным мной, и они могут появляться где угодно.

4b9b3361

Ответ 1

Для общего блага я повторю то, что вы уже знаете, и что @Rumbaruk уже упоминалось: gcc-документация явно ограничивает применение атрибута section глобальными переменными. Так требуемый обходной путь для gcc-поведения - это способ получить gcc не для barf или исправить неработающий код при неподдерживаемом приложении языка gcc-специфики расширение. Мы не имеем права ожидать успеха или ожидать, что успех будет последовательно повторяемым.

Вот длинное объяснение того, как и почему gcc создает конфликт типа раздела ошибка компиляции и clang. Прокрутите до Исправления, если они нетерпеливы, но не ожидайте серебряную пулю.

Для демонстрационных целей я буду работать с чуть более реалистичной программой, чем вы опубликовали, а именно:

source.cpp

const int* get_data()
{
    __attribute__((section(".custom")))
    static const int data = 123;

    return & data;
}

inline const int* inline_get_data()
{
    __attribute__((section(".custom")))
    static const int inline_data = 123;

    return & inline_data;
}

const int* other_get_data()
{
    return inline_get_data();
}

header.h

#ifndef HEADER_H
#define HEADER_H
extern const int* get_data();
extern const int* other_get_data();
#endif

main.cpp

#include "header.h"
#include <iostream>

int main()
{
    std::cout << (*get_data() + *other_get_data()) << std::endl;
    return 0;
}

В соответствии с этим, эта программа воспроизводит ошибку конфликта типа раздела, когда скомпилирован с gcc 5.2:

$ g++-5 -Wall -pedantic -c source.cpp
source.cpp:12:22: error: inline_data causes a section type conflict with data
     static const int inline_data = 123;
                      ^

У Кланга (3.6/3.7) нет жалоб:

$ clang++ -Wall -pedantic -I. -o prog main.cpp source.cpp 
$ ./prog
246

Корнем gcc-обструктивности является тот факт, что inline_get_data() является встроенная функция с внешней связью, которая связывает секцию связи к его статическим данным в той же самой системе перевода, что и не-встроенная функция, get_data(), который связывает тот же раздел привязки с его собственными статическими данными.

Компилятор использует разные правила для создания привязки для get_data() и inline_get_data() соответственно. get_data() - простой случай, inline_get_data() это сложный случай.

Чтобы увидеть разницу, пусть временно отключит конфликт раздела gcc, заменив "custom" на "custom.a" в get_data() и заменив "custom" на "custom.b" в inline_get_data().

Теперь мы можем скомпилировать source.cpp с помощью gcc и проверить соответствующие записи в таблице символов:

$ objdump -C -t source.o | grep get_data
0000000000000000 l     O .custom.a  0000000000000004 get_data()::data
0000000000000000 l    d  .text._Z15inline_get_datav 0000000000000000 .text._Z15inline_get_datav
0000000000000000 g     F .text  000000000000000b get_data()
0000000000000000 u     O .custom.b  0000000000000004 inline_get_data()::inline_data
0000000000000000  w    F .text._Z15inline_get_datav 000000000000000b inline_get_data()
000000000000000b g     F .text  000000000000000b other_get_data()

get_data(), конечно, был сделан глобальный символ (g) и get_data()::data сделанный локальный символ (l). Но inline_get_data() был сделан weak, ни глобальный, ни локальный символ (w) и inline_get_data()::inline_data, хотя это синтаксически статическая область кадра, был сделан уникальный глобальный символ (u). Это расширение GNU стандартного ELF привязки символов, требующие компоновщика времени выполнения, чтобы гарантировать, что символ уникален во всем времени выполнения.

В этих разных условиях сцепления для inline_get_data(), gcc справляется, поскольку считает это подходящим с тем, что функция встроена с внешней связью. Тот факт, что функция является встроенным, он должен быть определен в каждой единицы перевода в который он используется, и тот факт, что он имеет внешнюю связь, означает, что все эти определения должен обращаться к тому же inline_data()::get_data. Таким образом, статическая переменная блока-области должна, для ссылок, становятся общественным символом.

Из той же мотивации gcc по-разному относится к атрибутированному разделу custom.a в настройка get_data() и атрибут раздела custom.b в inline_get_data(). Назначив inline_get_data()::inline_data уникальный глобальный символ, он хочет убедитесь, что несколько определений этого символа не введены посредством несколько копий раздела custom.b из разных единиц перевода. С этой целью применяет атрибут GROUP linker к custom.b: это (пропуская детали) позволяет это для создания директивы .section, которая присваивает custom.b именованной группе разделов и позволяет компоновщику сохранить только одну копию этой группы разделов. Обратите внимание:

$ readelf -t source.o
...
...
[ 7] .custom.a
   PROGBITS               PROGBITS         0000000000000000  0000000000000068  0
   0000000000000004 0000000000000000  0                 4
   [0000000000000002]: ALLOC
[ 8] .custom.b
   PROGBITS               PROGBITS         0000000000000000  000000000000006c  0
   0000000000000004 0000000000000000  0                 4
   [0000000000000202]: ALLOC, GROUP
                              ^^^^^
 ...
 ...

И это триггер ошибки конфликта типа раздела, когда custom.a и custom.b являются одними и теми же. Gcc не может создать раздел, который имеет и не имеет GROUP атрибут.

Теперь, если get_data() и inline_get_data были определены в разных единицах перевода, компилятор не мог заметить конфликт. Так какое это имеет значение? Что пойдет не так, как в этот случай?

В этом случае ничего не получается, потому что в этом случае конфликт типа раздела не существует. Раздел custom, сгенерированный gcc в source.o, представляет собой раздел в source.o. Это должно либо имеют или не имеют атрибут GROUP, но в любом случае нет конфликта с раздел custom с тем же именем в other_source.o, имеющий противоположный статус. Эти представляют собой различные секции ввода для компоновщика. Он будет дедуплицировать разделы ввода custom которые GROUP ed, сохраняя только одно из них на имя группы. Это не будет сделано с входными разделами custom, которые не являются GROUPed, и, наконец, он будет сливаться все входные разделы custom остаются в одном выходном разделе custom в двоичном формате, с теперь неприменимым атрибутом GROUP ditched. Этот вывод custom содержат get_data()::data в качестве локального символа и inline_get_data()::inline_data как уникальный глобальный символ. Конфликт состоит исключительно в компиляторе, сталкивающемся с противоречивыми правилами относительно того, будет ли раздел source.o(custom) должно быть GROUP ed или нет.

Почему же это не противоречит тому же противоречию? Это потому, что clang принимает более простой, но несколько менее надежный подход к проблеме встроенной функции с внешней связью содержащие статические данные.

Придерживаясь дифференциации разделов custom.a и custom.b, теперь скомпилируем source.cpp с помощью clang и проверим соответствующие характеристики символа и раздела:

$ objdump -C -t source.o | grep get_data
0000000000000000 l     O .custom.a  0000000000000004 get_data()::data
0000000000000000 l    d  .text._Z15inline_get_datav 0000000000000000 .text._Z15inline_get_datav
0000000000000010 g     F .text  000000000000000b other_get_data()
0000000000000000  w    F .text._Z15inline_get_datav 0000000000000010 inline_get_data()
0000000000000000 g     F .text  0000000000000010 get_data()
0000000000000000  w    O .custom.b  0000000000000004 inline_get_data()::inline_data

Там одно отличие от выхода gcc. Как и следовало ожидать, clang не помогает связанного с GNU символа, связывающего уникальный глобальный символ (u) для inline_get_data()::inline_data. Это делает слабый символ, например inline_get_data().

И для характеристик раздела мы имеем:

$ readelf -t source.o
...
...
[ 8] .custom.a
   PROGBITS               PROGBITS         0000000000000000  0000000000000080  0
   0000000000000004 0000000000000000  0                 4
   [0000000000000002]: ALLOC
[ 9] .custom.b
   PROGBITS               PROGBITS         0000000000000000  0000000000000084  0
   0000000000000004 0000000000000000  0                 4
   [0000000000000002]: ALLOC
...
...

Никакой разницы, поэтому никакого конфликта. Поэтому мы можем заменить имена разделов custom.a и custom.b с custom, на оригинал и успешно скомпилировать.

Clang полагается на слабое связывание inline_get_data()::inline_data с ответьте на требование, чтобы только один такой символ решался каждой реализацией of inline_get_data(), который попадает в связь. Это экономит его от типа сечения конфликт, но превозмогает более сложный подход gcc более сложного подхода.

Можете ли вы сказать gcc, чтобы отказаться от этой надежности и сделать clang-подобный способ с компиляцией inline_get_data()? Вы можете немного, но недостаточно. Вы можете указать gcc вариант -fno-gnu-unique, чтобы дать команду компилятору забыть GNU-specfic уникальный глобальный привязка символов. Если вы это сделаете, то он сделает inline_get_data()::inline_data слабый символ, как clang; но это не подтолкнет его - возможно, это должно - отказаться от группировки разделов привязка для атрибутного раздела символа, и вы все равно получите тип раздела конфликт. Я не могу найти возможности запретить это довольно резкое генерирование кода поведение для вашего, по общему признанию, вонючего кода проблемы.

затруднительного

Мы видели, как и почему конфликт типа раздела gcc вызван наличие в одной и той же единице перевода определений двух функций, одна встроенная с внешними связь, другая не встроенная, каждая из которых соответствует одному и тому же разделу привязки к его статическим данным.

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

Простой безопасный

Существует два способа определения конфликтующих функций в одном и том же единица перевода: -

  • Оба они определены в том же файле источника (.cpp).
  • Не встроенная функция определяется в исходном файле, который включает заголовок в который определяется встроенной функцией.

Если у вас есть случаи типа 1, то это просто goof со стороны тех, кто кодов исходный файл для кодирования встроенной функции с внешней связью. В этом если встроенная функция является локальной для ее единицы перевода и должна быть static. Если это сделанный static, тогда gcc внешние усилия связи исчезают, а тип сечения конфликт с ними. Вы сказали, что у вас нет контроля над кодом, в котором атрибут раздела материал макроинъекции, но его авторы должны быть восприимчивыми того факта, что встраивание внешних функций в исходный файл, а не заголовок является ошибкой и готов исправить его.

The Drastic Desperate One

Случаи типа 2 более вероятны. Для них, насколько я вижу, ваша надежда заключается в том, чтобы внедрить сборку в вашу сборку gcc так, чтобы директивы gcc .section относительно отнесенного раздела в определениях встроенных внешних функций программно отредактированный, чтобы быть clang-подобным перед созданием объектного кода.

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

Необходимым предварительным является изменение вашего макроса, который генерирует раздел custom атрибуты, так что вместо равномерного генерации имени раздела .custom вместо этого создается последовательность .custom.1, custom.2,..., custom.N при последовательных вызовах в перевод. Для этого используйте встроенный препроцессор __COUNTER__, например

#define CAT2(x,y) x##y
#define CONCAT(x,y) CAT2(x,y)
#define QUOT(x) #x
#define QUOTE(x) QUOT(x) 
#define SET_SECT() __attribute__((section(QUOTE(CONCAT(.custom.,__COUNTER__)))))

Точка этого только для того, чтобы использовать код предварительной обработки gcc, например:

const int* get_data()
{
    SET_SECT()
    static const int data = 123;

    return & data;
}

inline const int* inline_get_data()
{
    SET_SECT()
    static const int inline_data = 123;

    return & inline_data;
}

в код, например:

const int* get_data()
{

    __attribute__((section(".custom.0")))
    static const int data = 123;

    return & data;
}

inline const int* inline_get_data()
{

    __attribute__((section(".custom.1")))
    static const int inline_data = 123;

    return & inline_data;
}

которые не будут вызывать конфликты типа секции.

С этим на месте и применяется к source.cpp, вы можете собрать файл с помощью gcc:

g++ -S source.cpp

и наблюдать на выходе source.s, что непроблематичный раздел custom.0 получите директиву .section:

.section    .custom.0,"a",@progbits

тогда как проблемный раздел custom.1 получает:

.section    .custom.1,"aG",@progbits,_ZZ15inline_get_datavE11inline_data,comdat

где _ZZ15inline_get_datavE11inline_data - имя группы разделов и comdat сообщает компоновщику о дедупликации этой секции-группы.

Повторите это с помощью clang и обратите внимание, что соответствующие директивы:

.section    .custom.0,"a",@progbits
.section    .custom.1,"a",@progbits

без каких-либо различий, кроме имени раздела.

Таким образом, требуемый для сборки взлома - это тот, который будет выглядеть так:

.section    .custom.0,"a",@progbits
.section    .custom.1,"aG",@progbits,_ZZ15inline_get_datavE11inline_data,comdat

в

.section    .custom,"a",@progbits

Это может быть выражено заменой sed:

s|^\t\.section\t\.custom\.[0-9]\{1,\},"a\(G\)*",@progbits.*$|\t\.section\t\.custom,"a",@progbits|g

Для демонстрационной программы, предполагая необходимые изменения для макроустройства, Резкое решение можно сформулировать в make файле так:

CXX ?= g++
SRCS = main.cpp source.cpp
ASMS = $(SRCS:.cpp=.s)
OBJS = $(SRCS:.cpp=.o)
CPPFLAGS = -I.
CXXFLAGS = -fno-gnu-unique

%.o: %.cpp

%.s: %.cpp
%.s: %.cpp
    $(CXX) $(CPPFLAGS) $(CXXFLAGS) -S -o [email protected] $<

%.o: %.s    
%.o: %.s
    sed -i 's|^\t\.section\t\.custom\.[0-9]\{1,\},"a\(G\)*",@progbits.*$$|\t\.section\t\.custom,"a",@progbits|g' $<
    $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o [email protected] $<

.PHONY: all clean
.INTERMEDIATE: $(ASMS)

all: prog

prog: $(OBJS)
    $(CXX) -o [email protected] $^

clean:
    rm -f prog $(OBJS) $(ASMS)

из которого a ./prog может быть построено с помощью gcc, который удовлетворяет ожиданиям печати 246 по стандарту.

Обратите внимание на три детали файла makefile: -

  • Нам нужно написать пустые правила шаблонов, например %.o: %.cpp, чтобы удалить make inbuilt рецепты для этих правил.
  • В команде sed нам нужно *$$ как маркер eol, чтобы избежать make-расширения $.
  • -fno-gnu-unique передается в флагах компилятора для заполнения мимики clang.

Это не решение, которое я бы хотел разоблачить для открытой пользовательской базы, за исключением пробела. Я не буду demur, если убрать его из всего: нет ли лучшего способа атаковать лежащую в основе проблему?

Ответ 2

Взаимодействие с настраиваемыми разделами является немного грязной темой, потому что gcc принимает решение о данных или bss в зависимости от значения init и не ожидает, что вы столкнетесь с ним на этом уровне.

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

Маленькая пользовательская библиотека может быть построена с помощью -fno-zero-initialized-in-bss, чтобы иметь все пользовательские данные в разделе данных для легкого разбора. Но не делайте этого в своих двоичных файлах.

Ответ 3

Наконец-то я нашел удовлетворительное решение. Это действительно просто сочетание уже известных методов. В рабочей среде используется обычная статическая переменная, адрес которой помещается в пользовательский раздел встроенной сборкой. На разных платформах (clang, MSVC), __attribute__ или #pragma могут использоваться с теми же результатами без ASM. Это решение можно легко обернуть в общий агностический макрос платформы.

const int* get_data()
{
  static const int data = 123;
  __asm__(
    ".pushsection .custom, \"?\", @progbits" "\n"
    ".quad %c0" "\n"
    ".popsection" "\n"
    : : "i"(&data)
  );

  return & data;
}

inline const int* inline_get_data()
{
  static const int inline_data = 123;
  __asm__(
    ".pushsection .custom, \"?\", @progbits" "\n"
    ".quad %c0" "\n"
    ".popsection" "\n"
    : : "i"(&inline_data)
  );

  return & inline_data;
}

int main()
{
  (void) get_data();
  (void) inline_get_data();
  return 0;
}

Ответ 4

Документация gcc (например, для 5.3):

Используйте атрибут раздела с глобальными переменными, а не локальными переменными [...]

Таким образом, вам нужно извлечь эти переменные из функций:

__attribute__((section(".custom"))) static const int data = 123;
__attribute__((section(".custom"))) static const int inline_data = 123;

const int* get_data()
{
  return &data;
}

inline const int* inline_get_data()
{
  return &inline_data;
}

int main()
{
  (void)get_data();
  (void)inline_get_data();
} 

Это компилируется с помощью gcc-5.2 и clang-3.5.1