c++ - programacion - Problemas al lanzar y atrapar excepciones en OS X con-fno-rtti
manual programacion android español pdf (3)
Bueno, aunque he aceptado una respuesta, no resolvió todos los problemas. Así que estoy escribiendo la solución que funcionó al final.
Hice una pequeña herramienta para postprocesar los archivos de objetos y marcar los símbolos locales como UNDEF
. Esto obliga al enlazador a usar definiciones de libstdc++
y no las locales del archivo. El enfoque básico de la herramienta es:
- cargar el encabezado de Mach-O
- camina los comandos de carga y encuentra el comando
LC_SYMTAB
- cargar la lista de símbolos (
struct nlist
) y las cadenas - recorra los símbolos y busque los que necesitamos (por ejemplo,
__ZTISt9bad_alloc
) - establezca el tipo de los símbolos encontrados en
N_UNDF|N_EXT
. - después del procesamiento, vuelva a escribir la tabla de símbolos modificada en el archivo.
(También hice una implementación similar para ELF)
Yo postprimo cualquier archivo que use excepciones estándar, ya sea para lanzar o atrapar. Para asegurarme de que la lista de archivos no esté obsoleta, agregué una verificación posterior al enlace para detectar símbolos locales no deseados usando nm
.
Esto parece resolver todos los problemas que he tenido hasta ahora.
El problema es algo similar a esta pregunta, pero la respuesta aceptada realmente no propone una solución o solución alternativa.
En nuestro proyecto, tenemos un dylib y el ejecutable principal. El dylib se compila con -fno-rtti
, mientras que el ejecutable utiliza RTTI. El problema ocurre cuando se lanza una excepción (por ejemplo, std::bad_alloc
) desde el dylib y se atrapa en el exe.
(Antes de que grites "¡las excepciones necesitan RTTI, así que debes tenerlas activadas!", Ten en cuenta que el RTTI necesario para las excepciones siempre se genera independientemente de la -frtti
o -fno-rtti
. Esto está documentado en realidad en -fno-rtti
Descripción de la bandera. El problema en OS X es que no se genera de la misma manera)
Después de una investigación, se descubrió lo siguiente:
- En el dylib (
-fno-rtti
), hay una copia local de las estructuras RTTI de la excepción; en particular, el símbolo__ZTISt9bad_alloc
(typeinfo for std::bad_alloc
). - El
-frtti
exe (-frtti
) importa el símbolo typeinfo delibstdc++.6.dylib
y no tiene una copia local
Dado que el código de manejo de excepciones se basa en la comparación de punteros de información de tipo para determinar la coincidencia de excepción, la coincidencia falla y solo la catch(...)
correctamente.
Hasta ahora veo las siguientes opciones:
1) compilar todo, o al menos los archivos que lanzan y capturan excepciones, con -frtti
. Esto es factible, pero no me gusta la idea de generar RTTI para todo, incluso si no lo usamos; y la lista de archivos que funcionan con excepciones es propensa a quedar obsoleta.
2) al vincular el dylib, de alguna manera haga que el enlazador descarte la definición de excepción débil del archivo objeto y use la de libstdc++.6.dylib
. Hasta ahora no he tenido éxito.
3)?
Hice una pequeña prueba para ilustrar el problema.
--- throw.cpp ---
#include <iostream>
#if defined(__GNUC__)
#define EXPORT __attribute__((visibility("default")))
#else
#define EXPORT __declspec(dllexport)
#endif
EXPORT void dothrow ()
{
std::cout << "before throw" << std::endl;
throw std::bad_alloc();
}
--- main.cpp ---
#include <stdio.h>
#include <iostream>
#if defined(__GNUC__)
#define IMPORT extern
#else
#define IMPORT __declspec(dllimport)
#endif
IMPORT void dothrow ();
int main (void) {
try {
std::cout << "trying lib->main exception" << std::endl;
dothrow ();
}
catch ( const std::bad_alloc& )
{
std::cout << "caught bad_alloc in main - good." << std::endl;
}
catch (...)
{
std::cout << "caught ... in main - bad!" << std::endl;
}
}
--- makefile ---
# for main exe
CFLAGS_RTTI=-m32 -frtti -fvisibility=hidden -fvisibility-inlines-hidden -shared-libgcc -funwind-tables
# for dylib
CFLAGS_NORTTI=-m32 -fno-rtti -fvisibility=hidden -fvisibility-inlines-hidden -shared-libgcc
# for linking; some switches which don''t help
CLFLAGS=-Wl,-why_live,-warn_commons,-weak_reference_mismatches,error,-commons,error
all: test
test: libThrow.dylib main.o
g++ $(CFLAGS_RTTI) -o test main.o -lthrow -L./ $(CLFLAGS)
main.o: main.cpp
g++ $(CFLAGS_RTTI) -c -o main.o main.cpp
throw.o: throw.cpp
g++ $(CFLAGS_NORTTI) -c -o throw.o throw.cpp
libThrow.dylib: throw.o
g++ $(CFLAGS_NORTTI) -dynamiclib -o libThrow.dylib throw.o
clean:
rm test main.o throw.o
Corriendo:
$ ./test
trying lib->main exception
before throw
caught ... in main - bad!
Símbolos de los archivos involucrados:
$ nm -m throw.o | grep bad_alloc
000001be (__TEXT,__textcoal_nt) weak private external __ZNSt9bad_allocC1Ev
000001be (__TEXT,__textcoal_nt) weak private external __ZNSt9bad_allocC1Ev
00000300 (__TEXT,__eh_frame) weak private external __ZNSt9bad_allocC1Ev.eh
(undefined) external __ZNSt9bad_allocD1Ev
00000290 (__DATA,__const_coal) weak external __ZTISt9bad_alloc
000002a4 (__TEXT,__const_coal) weak external __ZTSSt9bad_alloc
(undefined) external __ZTVSt9bad_alloc
$ nm -m libThrow.dylib | grep bad_alloc
00000ce6 (__TEXT,__text) non-external __ZNSt9bad_allocC1Ev
(undefined) external __ZNSt9bad_allocD1Ev (from libstdc++)
00001050 (__DATA,__const) weak external __ZTISt9bad_alloc
00000e05 (__TEXT,__const) weak external __ZTSSt9bad_alloc
(undefined) external __ZTVSt9bad_alloc (from libstdc++)
$ nm -m main.o | grep bad_alloc
(undefined) external __ZTISt9bad_alloc
$ nm -m test | grep bad_alloc
(undefined) external __ZTISt9bad_alloc (from libstdc++)
Nota: las opciones de compilación similares en Linux y Windows funcionan bien. Puedo lanzar excepciones desde un objeto / dll compartido y atraparlas en el archivo principal, incluso si están compiladas con diferentes -frtti
/ -fno-rtti
.
EDIT : así es como terminé resolviéndolo para el caso específico de bad_alloc
:
#if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION)
#define throw_nomem std::__throw_bad_alloc
#else
#define throw_nomem throw std::bad_alloc
#endif
EXPORT void dothrow ()
{
std::cout << "before throw" << std::endl;
throw_nomem();
}
La función __throw_bad_alloc
se importa de libstdc++.6.dylib
y, por lo tanto, siempre lanza un tipo correcto.
Simplemente puede mover toda su infraestructura de "excepciones de lanzamiento" a la biblioteca auxiliar con -frtti
habilitado - y vincularlo a otras cosas. Sin el código real es difícil decirlo, si esta descomposición es posible o no.
Aquí hay un código de ejemplo:
// Thrower.cc
void DoThrow() {
throw std::bad_alloc;
}
// LibraryNoRTTI.cc
void f() {
DoThrow();
}
// main.cc
int main() {
try {
f();
}
catch(std::bad_alloc&) {}
return 0;
}
La forma más sencilla es mover todas sus invocaciones de throw
a las funciones separadas con los tipos adecuados, como: throw std::logical_error("message");
va a void ThrowLogicError(const std::string& message) { ... }
Si hay un problema con la encapsulación (clases de excepciones privadas), entonces puede hacer amigos con funciones de lanzamiento.
Si aún desea usar excepciones ( throw
/ catch
) dentro de la biblioteca que no es rtti, entonces debe hacer una separación entre las excepciones internas y las excepciones utilizadas en la API de su biblioteca.
La buena manera es utilizar el método de catch
nativo de C ++ para fines internos, y luego volver a emitir algunas excepciones, utilizando funciones de biblioteca basadas en rtti, hacia el exterior, según su interfaz:
// Thrower.cc
void Rethrow(const std::exception& e) {
throw e;
}
// LibraryNoRTTI.cc
namespace {
void internal_stuff() {
throw std::logical_error("something goes wrong!");
}
} // namespace
// You even may explicitly specify the thrown exceptions in declaration:
void f() throw(std::logical_error) {
try {
internal_stuff();
}
catch(std::exception& e) {
Rethrow(std::logical_error(std::string("Internal error: ") + e.what());
}
}
Inicio Editar Marzo 4, 2014
Creo que el compilador Clang ++ tiene más posibilidades de obtener el manejo de excepciones deseado. He encontrado esta publicación de desbordamiento de pila: Clang y el compilador predeterminado en OS X Lion . La publicación tiene líneas de guión útiles para modificar ~/.bashrc
para cambiar la configuración predeterminada del compilador del sistema en Snow Leopard y cómo utilizar el GCC de LLVM. Para Clang, agregue dentro de ~/.bashrc
:
# Set Clang as the default compiler for the system
export CC=clang
export CFLAGS=-Qunused-arguments
export CPPFLAGS=-Qunused-arguments
Si el enlace simbólico de c++
no está presente, llame directamente a clang ++ o agregue el enlace de c ++ como desee (por ejemplo,
ln -s /usr/bin/clang++ c++
). Es una buena idea comprobar todos los enlaces simbólicos dentro de /usr/bin
ejecutando:
ls -l `which lynx` | more
En mis herramientas de línea de comandos de Mavericks, la instalación de c++
apunta a clang++
y cc
apunta a clang
. La versión del compilador g++
dice:
$ g++ --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx- include-dir=/usr/include/c++/4.2.1
Apple LLVM version 5.0 (clang-500.2.79) (based on LLVM 3.3svn)
Target: x86_64-apple-darwin13.0.0
Thread model: posix
La versión compatible con clang++
dice:
$clang++ --version
Apple LLVM version 5.0 (clang-500.2.79) (based on LLVM 3.3svn)
Target: x86_64-apple-darwin13.0.0
Thread model: posix
Tenga en cuenta que la ruta del directorio de inclusión de g++
está establecida en /usr/include/c++/4.2.1
, probablemente no sea la ruta de inclusión necesaria para resolver el problema.
MacPorts: Esperemos que la respuesta para cualquier versión de OS X
La mejor solución que puedo encontrar para obtener cualquier versión del compilador Clang ++ para cualquier versión de OS X es usar la herramienta de código abierto llamada MacPorts . Existe una extensa documentación en la Guía de MacPorts . La aplicación se denomina port
y se puede instalar desde un paquete de instalación de OS X u obtener el código fuente y compilar localmente. Lo siguiente es de instalar MacPorts en Snow Leopard. Las otras versiones de OS X deberían ser similares. Después de obtener MacPorts para Snow Leopard, ejecute el comando de búsqueda de puertos para observar todos los diferentes puertos disponibles relacionados con el sonido. Por ejemplo, se ve así:
$port search clang
La lista parcial de resultados de búsqueda de Snow Leopard 10.6.8 es:
clang-2.9 @2.9_13 (lang)
C, C++, Objective C and Objective C++ compiler
clang-3.0 @3.0_12 (lang)
C, C++, Objective C and Objective C++ compiler
clang-3.1 @3.1_7 (lang)
C, C++, Objective C and Objective C++ compiler
clang-3.2 @3.2_2 (lang)
C, C++, Objective C and Objective C++ compiler
clang-3.3 @3.3_2 (lang)
C, C++, Objective C and Objective C++ compiler
clang-3.4 @3.4 (lang)
C, C++, Objective C and Objective C++ compiler
clang-3.5 @3.5-r202097 (lang)
C, C++, Objective C and Objective C++ compiler
clang_select @0.1 (sysutils)
common files for selecting default clang version
Luego instalé con éxito clang-3.3 con: sudo port install clang-3.3
. Una vez que se complete, vea las versiones disponibles escribiendo port select --list clang
. A continuación, ejecute el
sudo port select --set clang mp-clang-3.3
o similar. Cuando ejecuto clang++ --version
dice (como se espera):
clang version 3.3 (tags/RELEASE_33/final)
Target: x86_64-apple-darwin10.8.0
Thread model: posix
Lo mismo para cuando se ejecuta el comando clang --version
(después de cerrar y reiniciar el terminal):
clang version 3.3 (tags/RELEASE_33/final)
Target: x86_64-apple-darwin10.8.0
Thread model: posix
Hay paquetes de instalación de MacPorts para muchas versiones de OS X (por ejemplo, Leopard, Snow Leopard, Lion, Mountain Lion, Mavericks, etc.). No fui más atrás que Leopard con mi búsqueda. Si usa un OS X más antiguo que Leopard, revise el sitio de MacPorts a fondo.
Si tengo curiosidad acerca de los detalles sobre dónde encontrar Xcode 4.2 (o solía poder obtenerlo), he encontrado esta publicación con respecto a la obtención de Xcode 4.2 para Snow Leopard. Xcode 4.2 para Snow Leopard . Luego estos dos adicionales: ¿Puedo usar las últimas funciones de C ++ 11 en XCode 4 o OSX Lion? [duplicar] y ¿Puedo usar C ++ 11 con Xcode? . Después de probar un par de enlaces para ver si el 4.2código X permanece disponible para Snow Leopard, no hay alegría.
Es muy probable que la instalación de MacPorts libc ++ sea necesaria para tener soporte completo de C ++ 11. Para instalar la versión más reciente, ejecute sudo port install libcxx
. El contenido de /usr/lib
se sobrescribirá con las bibliotecas actuales de C ++ 11 (según sea necesario según el ticket n. ° 42385 de MacPorts: libcxx / libcxxabi: la actualización del sistema operativo puede inutilizar el sistema
Si parece que todavía falta libc ++, intente esto: "libc ++" Biblioteca estándar de C ++ . Entonces usa esto:
$ export TRIPLE=-apple-
$ export MACOSX_DEPLOYMENT_TARGET=10.6
$ ./buildit
de Cómo construir libc ++ con LLVM / Clang 3.3 en Mac OS X 10.6 "Snow Leopard" .
En OS X Lion, Mountain Lion y Mavericks, todos ellos tienen descargas recientes de herramientas de línea de comandos independientes en el sitio de desarrolladores de Apple. La versión de Clang puede ser más antigua de lo que se necesita, así que asegúrese de confirmar qué funciones de C ++ 11 se necesitan al usar el Clang de las herramientas de línea de comandos del sitio del desarrollador.
Edición final 4 de marzo de 2014
La detección de macros anterior puede necesitar cambiar de __GNUC__
a __clang__
o __clang_version__
. Todo depende de cuáles sean las macros de compilador predefinidas para cada compilador OS X, y la mejor manera de detectarlas según sea necesario aquí. La respuesta de desbordamiento de pila en: ¿Qué macro predefinida puedo usar para detectar clang? debería ser útil para configurar la línea de comandos para obtenerlos (por ejemplo, clang++ -dM -E -xc /dev/null
).
Noté que al ejecutar el comando de ejemplo anterior hay una macro predefinida llamada __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
. En el clang++
los Mavericks, el valor macro es 1090
. Puede ser necesario tener una familia de lógica #ifdef
para establecer la macro EXPORT
apropiada para cada compilador OS X clang ++.