Ответ 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, если убрать его из всего: нет ли лучшего способа атаковать лежащую в основе проблему?