c++ - programacion - variable static c#
Los datos estáticos en línea causan un conflicto de tipo de sección (4)
Quiero poner algunos datos definidos por el usuario en una sección personalizada para ser leídos por la aplicación y un analizador fuera de línea al mismo tiempo. Suponiendo la siguiente muestra:
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;
}
El valor de data
y inline_data
aparecerá en la sección .custom
. Clang compila este ejemplo y produce el resultado correcto, al igual que MSVC, cuando los __attributes__
son reemplazados por los pragmas correspondientes .
Desafortunadamente, GCC 5.2 da el siguiente error:
error: inline_data causes a section type conflict with data
El problema se reduce al hecho de que las dos variables tienen un vínculo diferente (los data
están en una sección marcada con a
, la sección de inline_data
está marcada con aG
). GCC 4.9 falla de la misma manera si la segunda función no está marcada como en línea sino que es una plantilla (GCC 5.2 compila eso).
GCC 5.2 también compila bien si el nombre de una sección se cambia temporalmente y se fija manualmente en el ensamblaje generado.
¿Hay alguna solución conocida para este problema? No tengo control sobre la firma de la función, las *data
variables de *data
son producidas por una macro proporcionada por mí, y pueden aparecer en cualquier lugar.
Jugar con secciones personalizadas es un tema un poco sucio porque gcc decide sobre datos o bss dependiendo del valor de init y no espera que te metas con ese nivel.
Lo que sugeriría para los datos del usuario es usarlo como normalmente usa datos; colóquelo en un archivo de datos. Si insistes en usar la biblioteca, al menos puedes dejar que use su propia biblioteca y luego los datos pueden estar en los lugares normales.
Se puede crear una pequeña lib de usuario con -fno-zero-initialized-in-bss
para tener todos los datos de usuario en la sección de datos para un fácil análisis. Pero no hagas eso en tus binarios.
La documentación de gcc (por ejemplo, para 5.3 ) dice:
Utilice el atributo de sección con variables globales y no variables locales [...]
Por lo tanto, necesita extraer esas variables de las funciones:
__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();
}
Esto compila muy bien con gcc-5.2 y clang-3.5.1
Por el bien general, reiteraré lo que ya sabe y lo que @Rumbaruk ya ha citado: la documentación de gcc restringe explícitamente la aplicación del atributo de section
a las variables globales . Entonces, la solución deseada para el comportamiento de gcc es una manera de hacer que gcc no interfiera o emita código roto en una aplicación no compatible de una extensión de lenguaje específica de gcc. No tenemos derecho a esperar el éxito ni a esperar que el éxito sea repetible constantemente.
Aquí viene una larga explicación de cómo y por qué gcc produce el error de compilación de conflicto de tipo sección y clang no. Desplácese hasta Correcciones si es impaciente, pero no espere una bala de plata.
Para fines de demostración, trabajaré con un programa un poco más realista de lo que ha publicado, a saber:
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;
}
Tal como está, este programa reproduce el error de conflicto de tipo de sección cuando se compila con 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;
^
Clang (3.6 / 3.7) no tiene quejas:
$ clang++ -Wall -pedantic -I. -o prog main.cpp source.cpp
$ ./prog
246
La raíz de obstruccionismo de gcc es el hecho de que inline_get_data()
es una función en línea con enlace externo que atribuye una sección de enlace a sus datos estáticos en la misma unidad de traducción que una función no en línea , get_data()
, que atribuye la misma sección de enlace a sus propios datos estáticos.
El compilador adopta diferentes reglas para generar el enlace para get_data()
y inline_get_data()
respectivamente. get_data()
es el caso simple, inline_get_data()
es un caso complicado.
Para ver la diferencia, "custom.a"
temporalmente el conflicto de la sección gcc reemplazando "custom"
por "custom.a"
en get_data()
y reemplazando "custom"
por "custom.b"
en inline_get_data()
.
Ahora podemos compilar source.cpp
con gcc e inspeccionar las entradas de la tabla de símbolos relevantes:
$ 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()
, por supuesto, se ha convertido en un símbolo global ( g
) y get_data()::data
hecho un símbolo local ( l
). Pero inline_get_data()
se ha convertido en un símbolo débil , ni global ni local ( w
) y inline_get_data()::inline_data
, aunque sintácticamente es bloque-scope estático, se ha convertido en un símbolo global único ( u
). Esta es una extensión de GNU de los enlaces de símbolos ELF estándar que requieren el enlazador en tiempo de ejecución para garantizar que el símbolo sea único en todo el enlace de tiempo de ejecución.
En estas diferentes estipulaciones de enlace para inline_get_data()
, gcc está haciendo frente, como lo considere apropiado, al hecho de que la función está en línea con la vinculación externa . El hecho de que la función esté en línea significa que debe definirse en cada unidad de traducción en la que se utiliza, y el hecho de que tiene una vinculación externa significa que todas esas definiciones deben abordar la misma inline_data()::get_data
. Por lo tanto, la variable estática de ámbito de bloque debe convertirse, para fines de vinculación, en un símbolo público.
De la misma motivación, gcc trata de manera diferente con la sección de atributos custom.a
en la configuración de get_data()
y la sección de atributos custom.b
en inline_get_data()
. Habiendo designado inline_get_data()::inline_data
un símbolo global único, quiere asegurarse de que múltiples definiciones de este símbolo no sean introducidas por el enlace de copias múltiples de la sección custom.b
de diferentes unidades de traducción. Para ello, aplica el atributo del vinculador GROUP
a custom.b
: esto (omitir detalles) le permite generar una directiva .section
que asigna custom.b
a un grupo-sección con nombre y le indica al enlazador que retenga solo una copia de ese grupo de sección. Observar:
$ 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
^^^^^
...
...
Y este es el desencadenante del error de conflicto de tipo sección, cuando custom.a
y custom.b
son uno y el mismo. Gcc no puede crear una sección que tenga y no tenga el atributo GROUP
.
Ahora bien, si get_data()
y inline_get_data
se definieron en diferentes unidades de traducción, el compilador no pudo notar el conflicto. así que .. ¿Qué importa? ¿Qué iría mal en ese caso?
No ocurre nada en ese caso, porque en ese caso no hay conflictos de tipo de sección. Una sección custom
generada por gcc en source.o
es una sección en source.o
. Debe tener o no el atributo GROUP
, pero de cualquier manera no hay conflicto con una sección custom
del mismo nombre en other_source.o
tenga el estado opuesto. Estas son secciones de entrada distintas para el enlazador. Deduplicará las secciones de entrada custom
que son GROUP
ed, conservando solo una de ellas por nombre de grupo. No hará eso con las secciones de entrada custom
que no están GROUPed
, y finalmente fusionará todas las secciones de entrada custom
que quedan en una sección custom
salida en el binario, con los atributos de GROUP
ahora no aplicables abandonados . Esa sección custom
salida contendrá get_data()::data
como un símbolo local y inline_get_data()::inline_data
como un símbolo global único. El conflicto consiste únicamente en que el compilador encuentre reglas contradictorias en cuanto a si la sección source.o(custom)
debe ser GROUP
ed o no.
¿Por qué entonces no choca la misma contradicción? Es porque clang adopta un enfoque más simple pero algo menos robusto para el problema de una función en línea con enlaces externos que contienen datos estáticos .
Siguiendo con la diferenciación de las secciones custom.a
y custom.b
, custom.b
source.cpp
con clang e inspeccionaremos el símbolo relevante y las características de la sección:
$ 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
Hay una diferencia allí desde la salida de gcc. Como cabría esperar, clang no inline_get_data()::inline_data
símbolo específico de GNU que vincula el símbolo global único ( u
) para inline_get_data()::inline_data
. Hace que sea un símbolo débil, como inline_get_data()
sí mismo.
Y para los rasgos de la sección tenemos:
$ 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
...
...
No hay diferencia, así que no hay conflicto. Es por eso que podemos reemplazar los nombres de las secciones custom.a
y custom.b
por custom
, por original y compilar con éxito.
Clang se basa en su enlace débil de inline_get_data()::inline_data
para responder al requisito de que cada implementación de inline_get_data()
entra en el enlace. Esto lo salva de un conflicto de tipo sección pero renuncia al blindaje de vinculación del enfoque más complicado de gcc.
¿Puedes decirle a gcc que renuncie a esa solidez y tome una forma parecida a la de un clang al compilar inline_get_data()
? Puedes un poco , pero no lo suficiente. Puede darle a gcc la opción -fno-gnu-unique
para indicarle al compilador que olvide el enlace de símbolo global único de GNU-specfic. Si lo haces, hará que inline_get_data()::inline_data
un símbolo débil, como clang; pero eso no lo empujará, tal vez debería, para soltar el enlace de agrupamiento de sección para la sección atribuida del símbolo, y seguirá teniendo el conflicto de tipo de sección. No puedo encontrar ninguna opción para inhibir este comportamiento de generación de código bastante básico por su código de problema que huele mal.
Correcciones
Hemos visto cómo y por qué el conflicto de tipo de sección gcc es provocado específicamente por la presencia en la misma unidad de traducción de definiciones de dos funciones, una en línea con enlace externo , la otra no en línea, cada una de las cuales atribuye la misma sección de enlace a sus datos estáticos.
Puedo sugerir dos remedios, uno de ellos simple y seguro pero aplicable solo a una variación del problema, el otro aplicable siempre, pero drástico y desesperado.
El simple seguro
Hay dos formas en que las definiciones de funciones conflictivas pueden entrar en la misma unidad de traducción:
- Ambos están definidos en el mismo archivo fuente (
.cpp
). - La función no en línea se define en un archivo fuente que incluye un encabezado en el que se define la función en línea.
Si tiene casos de tipo 1, entonces es solo un error de parte de quien codifica el archivo fuente codificar una función en línea con enlaces externos. En este caso, la función en línea es local para su unidad de traducción y debe ser static
. Si se convierte en static
, los esfuerzos de vinculación externa de gcc desaparecen y el tipo de sección entra en conflicto con ellos. Usted ha dicho que no tiene control sobre el código en el cual su parte atribuida es macroinyectada, pero sus autores deben ser receptivos al hecho de que escribir funciones externas en línea en un archivo fuente en lugar de un encabezado es un error y estar dispuesto a Corrígelo.
El drástico desesperado
Los casos de tipo 2 son más probables. Para estos, por lo que puedo ver, su única esperanza es inyectar un montaje en su compilación de gcc para que las directivas de sección de gcc con respecto a la sección atribuida en las definiciones de funciones externas en línea se editen programáticamente para que suenen como un clang antes de que el objeto el código se genera
Claramente, tal solución será viable solo para un conjunto de versiones de gcc que usted sabe que generan el "patrón correcto de directivas de .section
equivocadas" en las cuales apuntar a su .section
correctivo, y un sistema de compilación que lo use debe verificar versión operativa de gcc por adelantado.
Un preliminar necesario es modificar su macro que genera los atributos de sección custom
para que en lugar de generar uniformemente el nombre de sección .custom
, en su lugar genere la secuencia .custom.1
, custom.2
, ..., custom.N
en las invocaciones sucesivas en una unidad de traducción Use el preprocesador incorporado __COUNTER__
para hacer esto, por ej.
#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__)))))
El objetivo de esto es simplemente dejar que gcc preproceda el código como:
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;
}
en código como:
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;
}
eso no provocará conflictos tipo sección.
Con esto en su lugar y aplicado a source.cpp
, puede armar el archivo con gcc:
g++ -S source.cpp
y observe en la source.s
salida que la sección no problemática custom.0
obtiene la directiva .section
:
.section .custom.0,"a",@progbits
mientras que la sección problemática custom.1
obtiene:
.section .custom.1,"aG",@progbits,_ZZ15inline_get_datavE11inline_data,comdat
donde _ZZ15inline_get_datavE11inline_data
es el nombre del grupo de sección y comdat
le dice al vinculador que deduplique este grupo de sección.
Repita esto con clang y observe que las directivas correspondientes son:
.section .custom.0,"a",@progbits
.section .custom.1,"a",@progbits
sin otra diferencia que el nombre de la sección.
Entonces, el hack de ensamblaje que requieres es uno que se convertirá en uno de los siguientes:
.section .custom.0,"a",@progbits
.section .custom.1,"aG",@progbits,_ZZ15inline_get_datavE11inline_data,comdat
dentro:
.section .custom,"a",@progbits
Esto se puede expresar mediante una sustitución sed
:
s|^/t/.section/t/.custom/.[0-9]/{1,/},"a/(G/)*",@progbits.*$|/t/.section/t/.custom,"a",@progbits|g
Para el programa de demostración, asumiendo los cambios necesarios en el macroamplificador, la solución drástica se puede formular en un archivo MAKE como ese:
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 $@ $<
%.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 $@ $<
.PHONY: all clean
.INTERMEDIATE: $(ASMS)
all: prog
prog: $(OBJS)
$(CXX) -o $@ $^
clean:
rm -f prog $(OBJS) $(ASMS)
desde donde se puede construir un ./prog
con gcc que cumple la expectativa de imprimir 246
en stdout.
Observe tres detalles del archivo MAKE:
- Necesitamos escribir reglas de patrones vacías como
%.o: %.cpp
para eliminar las recetas integradas de make para esas reglas. - En el comando
sed
, necesitamos*$$
como marcador eol para escapar de la expansión de$
. -
-fno-gnu-unique
se pasa en los indicadores del compilador para completar el mimetismo de clang.
Esta no es una solución que me gustaría exponer a una base de usuarios abierta excepto como stop-gap. No voy a objetar si el take-away de todo es: ¿No hay una mejor manera de atacar el problema subyacente?
Finalmente encontré una solución satisfactoria. En realidad, es solo la combinación de técnicas ya conocidas. El tiempo de ejecución utiliza una variable estática normal, cuya dirección se coloca en la sección personalizada mediante el ensamblaje en línea. En diferentes plataformas (clang, MSVC), __attribute__
o #pragma
se pueden usar con los mismos resultados, sin ASM. Esta solución se puede incluir fácilmente en una macro genérica, independiente de la plataforma.
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;
}