tutorial para guia español aprender c++ exception visibility symbols elf

c++ - guia - solidity para ethereum



Visibilidad de símbolos, excepciones, error de tiempo de ejecución (1)

Intento entender mejor la visibilidad de los símbolos. La Wiki de GCC ( http://gcc.gnu.org/wiki/Visibility ) tiene una sección sobre "Problemas con las excepciones de C ++". De acuerdo con GCC Wiki, es posible que haya errores de tiempo de ejecución debido a las excepciones no exportadas. Los errores de tiempo de ejecución sin tiempo de compilación error / advertencia es bastante peligroso, así que traté de entender mejor el problema. Hice algunos experimentos pero todavía no puedo reproducirlo. ¿Alguna idea de cómo reproducir el problema?

La Wiki menciona tres bibliotecas que se usan entre sí, así que hice tres pequeñas bibliotecas.

Ejecuto los siguientes comandos:

Clase de excepción sin vtable (funciona como se espera):

make ./dsouser

Clase de excepción con vtable pero no se exporta (ni siquiera se compila):

make HAS_VIRTUAL=1

Clase de excepción exportada vtable (funciona como se espera):

make HAS_VIRTUAL=1 EXCEPTION_VISIBLE=1 ./dsouser

Makefile:

CXX=g++-4.7.1 CFLAGS=-ggdb -O0 -fvisibility=hidden ifdef EXCEPTION_VISIBLE CFLAGS+=-DEXCEPTION_VISIBLE endif ifdef HAS_VIRTUAL CFLAGS+=-DHAS_VIRTUAL endif all: dsouser libmydso.so: mydso.cpp mydso.h $(CXX) $(CFLAGS) -fPIC -shared -Wl,-soname,$@ -o $@ $< libmydso2.so: mydso2.cpp mydso.h mydso2.h libmydso.so $(CXX) $(CFLAGS) -L. -fPIC -shared -Wl,-soname,$@ -o $@ $< -lmydso libmydso3.so: mydso3.cpp mydso.h mydso2.h mydso3.h libmydso2.so $(CXX) $(CFLAGS) -L. -fPIC -shared -Wl,-soname,$@ -o $@ $< -lmydso -lmydso2 dsouser: dsouser.cpp libmydso3.so $(CXX) $< $(CFLAGS) -L. -o $@ -lmydso -lmydso2 -lmydso3 clean: rm -f *.so *.o dsouser .PHONY: all clean

mydso.h

#ifndef DSO_H_INCLUDED #define DSO_H_INCLUDED #include <exception> #define SYMBOL_VISIBLE __attribute__ ((visibility ("default"))) namespace dso { class #ifdef EXCEPTION_VISIBLE SYMBOL_VISIBLE #endif MyException : public std::exception { public: #ifdef HAS_VIRTUAL virtual void dump(); #endif void SYMBOL_VISIBLE foo(); }; } #endif

mydso.cpp:

#include <iostream> #include "mydso.h" namespace dso { #ifdef HAS_VIRTUAL void MyException::dump() { } #endif void MyException::foo() { #ifdef HAS_VIRTUAL dump(); #endif } }

mydso2.h:

#ifndef DSO2_H_INCLUDED #define DSO2_H_INCLUDED #define SYMBOL_VISIBLE __attribute__ ((visibility ("default"))) namespace dso2 { void SYMBOL_VISIBLE some_func(); } #endif

mydso2.cpp:

#include <iostream> #include "mydso.h" #include "mydso2.h" namespace dso2 { void some_func() { throw dso::MyException(); } }

mydso3.h:

#ifndef DSO3_H_INCLUDED #define DSO3_H_INCLUDED #define SYMBOL_VISIBLE __attribute__ ((visibility ("default"))) namespace dso3 { void SYMBOL_VISIBLE some_func(); } #endif

mydso3.cpp:

#include <iostream> #include "mydso2.h" #include "mydso3.h" #include <iostream> namespace dso3 { void some_func() { try { dso2::some_func(); } catch (std::exception e) { std::cout << "Got exception/n"; } } }

dsouser.cpp:

#include <iostream> #include "mydso3.h" int main() { dso3::some_func(); return 0; }

Gracias dani


Soy el autor del parche original para GCC que agrega soporte de visibilidad de clase, y mi guía original sobre qué clones de GCC está en http://www.nedprod.com/programs/gccvisibility.html . Mi agradecimiento a VargaD por enviarme un correo electrónico personalmente para contarme esta pregunta de SO.

El comportamiento que observa es válido para GCC recientes, sin embargo, no siempre fue así. Cuando originalmente reparé GCC en 2004, envié una solicitud a GCC bugzilla para que el tiempo de ejecución del manejo de excepciones de GCC comparara los tipos lanzados mediante la comparación de cadenas de sus símbolos destrozados en lugar de comparar las direcciones de esas cadenas. Esto fue rechazado en ese momento por el GCC los mantenedores como un costo de tiempo de ejecución inaceptable, a pesar de que este comportamiento es lo que hace MSVC, y a pesar de que el desempeño durante los lanzamientos de excepciones generalmente no se considera importante dado que se supone que son raros. Por lo tanto, tuve que agregar una excepción específica a mi guía de visibilidad para decir que cualquier tipo de lanzamiento nunca debe estar oculto, ni una sola vez, en un binario como "ocultación" triunfa "por defecto", por lo que solo una declaración de símbolo oculta garantiza anular todos los casos de El mismo símbolo en un binario dado.

Lo que sucedió después, supongo que ninguno de nosotros lo esperaba: KDE aceptó públicamente mi función contribuida. Eso se convirtió en cascada en casi todos los grandes proyectos que utilizan GCC en un tiempo sorprendentemente corto. De repente, la ocultación de símbolos era la norma, no la excepción.

Desafortunadamente, un pequeño número de personas no aplicó mi guía correctamente para los tipos de excepción, y los informes de errores constantes sobre el manejo incorrecto de excepciones de objetos compartidos en GCC finalmente hicieron que los mantenedores de GCC se rindieran y muchos años más tarde se parchearan en la comparación de cadenas para el tipo de coincidencia arrojado, como lo había solicitado originalmente. Por lo tanto, en los CCG más nuevos, la situación es algo mejor. No he cambiado ni mi guía ni las instrucciones porque ese enfoque sigue siendo el más seguro para todos los GCC desde v4.0, y aunque los GCC más nuevos son más confiables en el manejo de lanzamientos de excepciones debido al uso de la comparación de cadenas, seguir las reglas de la guía no duele ese.

Esto nos lleva al problema typeinfo. Un gran problema es que la práctica recomendada de C ++ requiere que siempre herede virtualmente en tipos que se pueden lanzar, porque si compone dos tipos de excepción ambos heredados (digamos) de std :: exception, tener dos clases equidistantes de la base std :: exception causará un problema (std :: exception &) para auto call terminate () porque no puede resolver con qué clase base coincidir, por lo que solo debe tener una clase base std :: exception y la misma lógica se aplica a cualquier posible composición de lanzable tipo. Esta práctica recomendada es especialmente necesaria en cualquier biblioteca de C ++, porque no puede saber qué harán los usuarios de terceros con sus tipos de excepción.

En otras palabras, esto significa que todos los tipos de excepción lanzados en la mejor práctica siempre vendrán con una cadena de RTTI sucesiva para cada clase base, y que la coincidencia de excepciones ahora es un caso de hacer internamente un dynamic_cast <> exitoso al tipo que se está comparando, Una operación O (número de clases base). Y para que dynamic_cast <> funcione en una cadena de tipos heredados virtualmente, lo adivinaste, necesitas que cada una de esta cadena tenga visibilidad predeterminada. Si incluso uno está oculto del código que ejecuta el catch (), todo el caboodle se arruga y obtienes un terminate (). Me interesaría mucho si reescribiera su código de ejemplo anterior para heredar virtualmente y ver qué sucede: uno de sus comentarios dice que se niega a enlazar, lo cual es genial. Pero digamos que la DLL A define el tipo A, las subclases de DLL B, el tipo A en B, las subclases de DLL C, el tipo B en C, y el programa D intenta detectar una excepción de tipo A cuando se lanzó el tipo C. El programa D tendrá la información de tipo de A disponible, pero debería fallar al intentar obtener RTTI para los tipos B y C. Aunque, quizás, los CCG recientes también han solucionado esto. No lo sé, mi atención en los últimos años está en el colo, ya que ese es el futuro para todos los compiladores de C ++.

Obviamente, esto es un lío, pero es un lío específico de ELF: nada de esto afecta a PE o MachO, los cuales se resuelven correctamente al no usar tablas de símbolos globales de proceso en primer lugar. Sin embargo, el grupo de estudio de Módulos WG21 SG2 que trabaja en C ++ 17 debe implementar eficazmente las plantillas exportadas para que los módulos funcionen con el fin de resolver las violaciones de ODR, y C ++ 17 es el primer estándar propuesto que he visto escrito con un LLVM en mente. En otras palabras, los compiladores de C ++ 17 tendrán que volcar un AST complejo en un disco como lo hace el sonido metálico. Y eso implica un gran aumento en las garantías de lo que RTTI está disponible; de ​​hecho, es por eso que tenemos el grupo de estudio Reflexión del SG7, porque el AST de los módulos C ++ permite un gran aumento en las posibles oportunidades de auto reflexión. En otras palabras, espere que los problemas anteriores desaparezcan pronto con la adopción C ++ 17.

Así que, en resumen, sigue mi guía original por ahora. Y esperamos que las cosas mejoren enormemente en la próxima década. Y gracias a Apple por financiar esa solución, ha pasado mucho tiempo en llegar debido a lo difícil que es.

Niall