2010-04-15 16 views
5

Tengo un extraño código de auto modificación, pero en la raíz es un problema bastante simple: quiero poder ejecutar un jmp (o un call) y luego desde ese punto arbitrario arrojó una excepción y lo atrapó el bloque try/catch que contenía jmp/call.Lanzar una excepción de C++ después de un salto de asso en línea

Pero cuando hago esto (en gcc 4.4.1 x86_64) la excepción da como resultado un terminate() como lo haría si la excepción fuera lanzada desde fuera de un try/catch. Realmente no veo cómo esto es diferente a lanzar una excepción desde el interior de una biblioteca remota, pero obviamente es porque simplemente no funciona.

¿Cómo puedo ejecutar un jmp o call pero todavía arrojar una excepción al original try/catch? ¿Por qué este try/catch no sigue manejando estas excepciones como lo haría si la función se llamara normalmente?

El código:

#include <iostream> 
#include <stdexcept> 

using namespace std; 

void thrower() 
{ 
    cout << "Inside thrower" << endl; 
    throw runtime_error("some exception"); 
} 

int main() 
{ 
    cout << "Top of main" << endl; 

    try { 
     asm volatile (
      "jmp *%0" // same thing happens with a call instead of a jmp 
      : 
      : "r"((long)thrower) 
      : 
     ); 
    } catch (exception &e) { 
     cout << "Caught : " << e.what() << endl; 
    } 
    cout << "Bottom of main" << endl << endl; 
} 

La salida esperada:

Top of main 
Inside thrower 
Caught : some exception 
Bottom of main 

La salida real:

Top of main 
Inside thrower 
terminate called after throwing an instance of 'std::runtime_error' 
    what(): some exception 
Aborted 
+0

Originalmente, hice una pregunta similar en el contexto de los manejadores de señal en Linux. Eso simplemente nubló el problema, así que lo eliminé y le pedí esta versión mucho más simplificada. Todo el manejo de la señal estaba enmascarando mi verdadero problema. – SoapBox

+1

Existen dos maneras diferentes de implementar el manejo de excepciones. Uno es decorar el código y llamar a los sitios para impulsar el contexto en todas partes, y el otro es indexar la llamada y los puntos de retorno para que un manejador de excepciones pueda buscar dónde ir. De cualquier manera, al asm en línea le falta algo. – spraff

Respuesta

1

Si está utilizando gcc 4.4.7 (y superior) en Linux x86-64 , con mecanismo enana mango excepción (que podría ser la opción por defecto), tengo una manera de resolver esto .

Suponga que su código de ensamblaje en línea es una función inline_add. Llamará a otra función add, que podría arrojar una excepción. Aquí está el código:

extern "C" int add(int a, int b) { 
    throw "in add"; 
} 

int inline_add(int a, int b) { 
    int r = 0; 
    __asm__ __volatile__ (
     "movl %1, %%edi\n\t" 
     "movl %2, %%esi\n\t" 
     "call add\n\t" 
     "movl %%eax, %0\n\t" 
     :"=r"(r) 
     :"r"(a), "r"(b) 
     :"%eax" 
    ); 
    return r; 
} 

Si llama inline_add así:

try { 
    inline_add(1, 1); 
} catch (...) { 
    std::cout << "in catch" << std::endl; 
} 

que va de choque, porque gcc no proporciona el marco de excepción para inline_add. Cuando se trata de una excepción, tiene que crach. (Ver here para la 'compatibilidad con C')

así que tenemos que fingir un marco de excepción para él, pero sería difícil de piratear con el conjunto de gcc, sólo tiene que utilizar funciones con marco de excepción adecuado para rodearlo

se define una función como esta:

void build_exception_frame(bool b) { 
    if (b) { 
     throw 0; 
    } 
} 

y llamar inline_add así:

try { 
    inline_add(1, 1); 
    build_exception_frame(false); 
} catch (...) { 
    std::cout << "in catch" << std::endl; 
} 

y que sólo wo rks.

build_exception_frame debe venir después de la llamada, o no va a funcionar

Adicionalmente, para evitar optimiziong gcc podría asumir build_exception_frame, tenemos que añadir lo siguiente:

void build_exception_frame(bool b) __attribute__((optimize("O0"))); 

Puede comprobar el código de ensamblaje que genera gcc para verificar el código.

Parece que gcc proporciona el marco de excepción para el total try/catch, siempre que haya una función que pueda arrojar, y la posición importa.

Necesito ver cómo funciona gcc en eso más adelante.

Si alguien sabe acerca de eso, tenga la amabilidad de informarme. Gracias.

1

¿Ha mirado el código ensamblador generado por gcc si su intento {} bloque simplemente contenía una llamada de función? Estoy bastante seguro de que el compilador C++ hace algo más que un salto en ese punto, ya que necesita poder rastrear la pila cuando ocurre una excepción.

Su código podría funcionar si pudiera imitar los pasos que toma gcc al estructurar la llamada a la función.

Actualización:this question puede dar más información. En particular, la porción de lanzamiento y captura de Itanium ABI puede ser útil: link

+0

Esa pregunta no proporciona mucha más información. Mi débil intento por comprender el manejo de excepciones (en Mac OS X) me llevó a utilizar un extraño código 'libunwind', para el cual no pude encontrar el código abierto. Fue escrito en C++ (!) Y tenía convenciones de nomenclatura de Apple. Siendo así, no es estrictamente la funcionalidad de GCC. Mire en la fuente de GCC y verá que depende del libunwind proporcionado por el sistema operativo. – Potatoswatter

+0

No, parece que el asm es solo una llamada a la función (pongo comentarios en línea alrededor de la llamada, y no hay instrucciones entre los comentarios, excepto la propia llamada). – SoapBox

+0

@SoapBox: debe comparar las tablas de despacho de excepciones para ver qué funciones * * se insertan en 'try {' y 'throw' operan. – Potatoswatter

3

¿Ha visto cómo maneja su implementación las excepciones? Implica buscar direcciones de PC en tablas para averiguar qué estaba haciendo el programa en el lugar específico que arrojó, y lo que estaban haciendo todos los llamadores. Al menos en Mac OS X GCC.

El único otro sistema que he visto, Metrowerks Codewarrior para Mac (hacia atrás cuando) usaba un sistema similar, aunque algo más transparente. El compilador puede insertar funciones de forma transparente para cualquier tipo de cambio de contexto de excepción.

No tiene una oración para hacer que este portátil.

+0

Interesante ... Pero si incorporo una llamada legítima y funcional a "thrower()" dentro del mismo try/catch, la versión de inline-asm aún no funciona, lo cual suena como si dijera que debería hacerlo.Supongo que el manejador de excepciones podría estar mirando la dirección de la llamada en sí, que no es una que conozca. Esta es la respuesta más prometedora hasta ahora ... – SoapBox

1

Una vez que realiza el ensamblaje en línea, tiene un comportamiento definido en la implementación (7.4/1).

Debe intentar configurar un marco de pila; los detalles son específicos de la plataforma y no sé cómo hacerlo en x86_64.

Cuestiones relacionadas