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.
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
@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. –
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