2011-08-30 11 views
7

Estoy tratando de puerto algo de código usando VC++' Porting VC++ s try-excepto comunicado a MinGW:'__try s/__ excepto EXCEPTION_STACK_OVERFLOW a MinGW

bool success = true; 

__try { 
    //... 
} __except ((EXCEPTION_STACK_OVERFLOW == GetExceptionCode()) 
      ? EXCEPTION_EXECUTE_HANDLER 
      : EXCEPTION_CONTINUE_SEARCH) { 
    success = false; 
    _resetstkoflw(); 
} 
return success; 

¿Es posible escribir código que atrapa una excepción de desbordamiento de pila utilizando MinGW g ++?

+4

Siempre me ha parecido gracioso cómo las personas llaman a MÁS funciones cuando reciben una excepción de desbordamiento de pila ... Algunas excepciones son realmente fatales y deberían dejarse para matar el programa. – Blindy

+4

@Blindy: cuando se lanza la excepción, la pila se desenrolla y se alivia la condición de desbordamiento de la pila. Windows arroja esta excepción cuando aún queda una o dos páginas de pila; si tuvieras que apilar realmente el desbordamiento, tu proceso terminaría sin previo aviso. –

+0

No se desenrollará si ya estás en el marco de pila que rompió la espalda del camello proverbial. No sabía si todavía tenía algo de espacio extra después de la excepción, pero incluso si eso fuera cierto, ¿no obtendría la excepción nuevamente en el medio de 'GetExceptionCode'? – Blindy

Respuesta

9

Debería llamar manualmente a las funciones de la API de Windows que registran el manejo de excepciones; a saber, AddVectoredExceptionHandler. Tenga en cuenta que al usar MinGW que no respeta las excepciones de SEH, lanzar cualquier excepción de SEH o intentar capturar cualquier excepción dará como resultado un comportamiento indefinido, porque la semántica normal de desenrollado de la pila de C++ no se realiza. (¿Cómo sabe Windows destruir todos esos std::string en la pila?)

También necesitaría llamar al RemoveVectoredExceptionHandler al final del tiempo que desea que se llame al manejador de excepciones SEH.

En general, MinGW carece de soporte para las características de Windows como SEH y COM. ¿Alguna razón por la que intenta usar eso en lugar de MSVC++ (dado que ambos compiladores son gratuitos?)

+0

Hola Billy, gracias por tu respuesta. Tengo tanto MinGW g ++ como Visual C++ instalados, y uso ambos, excepto que quería experimentar con plantillas variadic, que VC++ aún no admite. –

+0

@Daniel: Hmm ... ¿quizás LLVM? Leí en alguna parte que pueden usar el CRT de VC++ y el SDK de Windows, pero no estoy seguro. –

1

MinGW no admite las palabras clave para las excepciones estructuradas; pero, como dice Billy O'Neal en su respuesta, puede llamar a ciertas funciones nativas para obtener el mismo efecto.

La pregunta es si desea el mismo efecto. Creo firmemente que las excepciones estructuradas son un error. El list of structured exceptions del que el sistema operativo le informará incluye cosas como "intentó dividir un número entero por 0", "no pudo usar el parámetro HANDLE pasado a una función", "intentó ejecutar una instrucción de código máquina ilegal" y " intentado acceder a la memoria sin permiso para hacerlo ". Realmente no puede hacer nada inteligente acerca de estos errores, pero las excepciones estructuradas le brindan la oportunidad de (1) reclamar que tiene y (2) permitir que el programa trabaje un poco más. Es mucho mejor averiguar por qué el código intentó dividir por 0, pasó un parámetro inválido HANDLE, intentó acceder a la memoria sin permiso para hacerlo, etc. y corrigió el código para nunca hacer eso.

Existe un argumento de que podría usar excepciones estructuradas para detectar problemas, mostrar un cuadro de diálogo y salir. No estoy seguro de cómo es mejor que dejar que el sistema operativo muestre un cuadro de diálogo y salir del programa (especialmente si el sistema operativo le envía un minivolcado en el proceso), que es el comportamiento predeterminado para las excepciones no controladas.

Some errors aren't recoverable.

+2

1. Puedes hacer SEH, es mucho más tedioso. (Ver la función a la que me he vinculado en mi respuesta) 2. EXCEPTION_STACK_OVERFLOW no está realmente sobrevolado; sin embargo, significa que has golpeado la última página de guardia en la pila. –

+0

Tienes razón; He editado mi respuesta. –

+0

Deberías señalar * por qué * crees que SEH es un error. Es un mecanismo "limpio" para proteger su aplicación contra los complementos mal codificados, ya que puede ajustar los puntos de entrada de los complementos en un bloque SEH y detectar las violaciones de acceso, desbordamientos de pila, etc. desencadenados por el complemento. Los usuarios no hacen distinción entre la aplicación de host y el complemento. Evitar que una aplicación se bloquee e informar una falla en el complemento puede mejorar la imagen de la aplicación y la probabilidad de obtener soporte. Apenas lo llamaría un "error". –

2

Es posible que desee consultar LibSEH para agregar compatibilidad de manejo de excepciones estructuradas para MinGW.

+0

Esto no es realmente correcto. Las extensiones de lenguaje de MSVC++ se aseguran de que se invoquen destructores C++ en la pila. Esta biblioteca simplemente llama AddVectoredExceptionHandler debajo de las cubiertas, y dará como resultado un comportamiento indefinido si la semántica de destrucción de la pila está en uso. –

+3

Cierto, pero dada la falta de soporte de MinGW para SEH, esto es casi tan bueno como lo que va a obtener. La página enlazada describe los problemas potenciales. –

+0

No diciendo lo contrario. Pero no obtiene lo que hace MSVC++, por lo que "agregar compatibilidad de manejo de excepciones estructuradas" es demasiado fuerte, no agrega nada a MinGW. Simplemente proporciona unas pocas macros que llaman a la API de Windows por usted. –

4

Esto no es bien conocida, pero el archivo de cabecera <excpt.h> en MinGW y MinGW-W64 proporciona macros __try1 y __except1 para producir ensamblado en línea gcc para el manejo de excepciones. Estas macros no están documentadas y no son fáciles de usar. Se pone peor. Las ediciones x86_64 de __try1 y __except1 no son compatibles con las ediciones de 32 bits. Usan diferentes devoluciones de llamada con diferentes argumentos y diferentes valores de retorno.

Después de unas horas, casi tenía código de trabajo en x86_64. Necesitaba declarar una devolución de llamada con el mismo prototipo que _gnu_exception_handler in MinGW's runtime.Mi devolución de llamada era

long CALLBACK 
ehandler(EXCEPTION_POINTERS *pointers) 
{ 
    switch (pointers->ExceptionRecord->ExceptionCode) { 
    case EXCEPTION_STACK_OVERFLOW: 
     return EXCEPTION_EXECUTE_HANDLER; 
    default: 
     return EXCEPTION_CONTINUE_SEARCH; 
    } 
} 

Y mi try-excepto el código era

__try1 (ehandler) { 
     sum = sum1to(n); 
     __asm__ goto ("jmp %l[ok]\n" :::: ok); 
    } __except1 { 
     printf("Stack overflow!\n"); 
     return 1; 
    } 
ok: 
    printf("The sum from 1 to %u is %u\n", n, sum); 
    return 0; 

Se estaba trabajando hasta Habilité optimización con gcc -O2. Esto causó errores de ensamblador por lo que mi programa ya no se compiló. Las macros __try1 y __except1 se rompen mediante una optimización en gcc 5.0.2 que mueve las funciones de .text a una sección diferente.

Cuando las macros funcionaban, el flujo de control era estúpido. Si sucedía un desbordamiento de la pila, el programa saltaba al __except1. Si no se produjo un desbordamiento de la pila, el programa cayó al __except1 en el mismo lugar. Necesitaba mi extraño __asm__ goto para saltar a ok: y evitar el desplome. No puedo usar goto ok; porque gcc eliminaría __except1 por ser inalcanzable.

Después de unas horas más, arreglé mi programa. Copié y modifiqué el código de ensamblado para mejorar el flujo de control (no más salto a ok:) y para sobrevivir a la optimización gcc -O2. Este código es feo, pero funciona para mí:

/* gcc except-so.c -o except-so */ 
#include <windows.h> 
#include <excpt.h> 
#include <stdio.h> 

#ifndef __x86_64__ 
#error This program requires x86_64 
#endif 

/* This function can overflow the call stack. */ 
unsigned int 
sum1to(unsigned int n) 
{ 
    if (n == 0) 
     return 0; 
    else { 
     volatile unsigned int m = sum1to(n - 1); 
     return m + n; 
    } 
} 

long CALLBACK 
ehandler(EXCEPTION_POINTERS *pointers) 
{ 
    switch (pointers->ExceptionRecord->ExceptionCode) { 
    case EXCEPTION_STACK_OVERFLOW: 
     return EXCEPTION_EXECUTE_HANDLER; 
    default: 
     return EXCEPTION_CONTINUE_SEARCH; 
    } 
} 

int main(int, char **) __attribute__ ((section (".text.startup"))); 

/* 
* Sum the numbers from 1 to the argument. 
*/ 
int 
main(int argc, char **argv) { 
    unsigned int n, sum; 
    char c; 

    if (argc != 2 || sscanf(argv[1], "%u %c", &n, &c) != 1) { 
     printf("Argument must be a number!\n"); 
     return 1; 
    } 

    __asm__ goto (
     ".seh_handler __C_specific_handler, @except\n\t" 
     ".seh_handlerdata\n\t" 
     ".long 1\n\t" 
     ".rva .l_startw, .l_endw, ehandler, .l_exceptw\n\t" 
     ".section .text.startup, \"x\"\n" 
     ".l_startw:" 
      :::: except); 
    sum = sum1to(n); 
    __asm__ (".l_endw:"); 
    printf("The sum from 1 to %u is %u\n", n, sum); 
    return 0; 

except: 
    __asm__ (".l_exceptw:"); 
    printf("Stack overflow!\n"); 
    return 1; 
} 

Usted podría preguntarse cómo Windows puede llamar ehandler() en una pila completa. Todas esas llamadas recursivas al sum1to() deben permanecer en la pila hasta que mi controlador decida qué hacer. Hay algo mágico en el kernel de Windows; cuando informa un desbordamiento de la pila, también asigna una página adicional de la pila para que ntdll.exe pueda llamar a mi controlador. Puedo ver esto en gdb, si pongo un punto de interrupción en mi controlador. La pila crece hacia la dirección 0x54000 en mi máquina. Los marcos de pila de sum1to() se detienen en 0x54000, pero el controlador de excepción se ejecuta en una página adicional de la pila de 0x53000 a 0x54000. Las señales de Unix no tienen esa magia, por lo que los programas Unix necesitan sigaltstack() para manejar un desbordamiento de pila.

+2

Interesante. +1 por ser un [Hombre Real] (http://www.ee.ryerson.ca/~elf/hack/realmen.html) y piratear cosas;) – alecov