これはあまり知られていませんが、<excpt.h>
MinGW と MinGW-w64 のヘッダー ファイルはマクロ__try1
を提供し__except1
、例外を処理するための gcc インライン アセンブリを生成します。これらのマクロは文書化されておらず、使いにくいです。ひどくなる。および の x86_64 エディションは、32 ビット エディションと互換性がありません__try1
。__except1
これらは、異なる引数と異なる戻り値を持つ異なるコールバックを使用します。
数時間後、x86_64 でほとんどコードが動くようになりました。_gnu_exception_handler
MinGW の runtimeと同じプロトタイプでコールバックを宣言する必要がありました。私のコールバックは
long CALLBACK
ehandler(EXCEPTION_POINTERS *pointers)
{
switch (pointers->ExceptionRecord->ExceptionCode) {
case EXCEPTION_STACK_OVERFLOW:
return EXCEPTION_EXECUTE_HANDLER;
default:
return EXCEPTION_CONTINUE_SEARCH;
}
}
そして、私のtry-exceptコードは
__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;
で最適化を有効にするまでは機能していましたgcc -O2
。これによりアセンブラーエラーが発生したため、プログラムがコンパイルされなくなりました。__try1
および__except1
マクロは、関数を別のセクションに移動する gcc 5.0.2 の最適化によって壊れています.text
。
マクロが機能したときの制御フローはばかげていました。スタック オーバーフローが発生した場合、プログラムは を飛び越えまし__except1
た。スタック オーバーフローが発生しなかった場合、プログラムは__except1
同じ場所に落ちました。__asm__ goto
ジャンプしok:
てフォールスルーを防ぐには、奇妙なものが必要でした。goto ok;
gccが到達不能で削除してしまうので使えません__except1
。
さらに数時間後、プログラムを修正しました。アセンブリ コードをコピーして変更し、制御フローを改善して ( にジャンプする必要がなくなりましたok:
)、gcc -O2
最適化を乗り切りました。このコードは醜いですが、私にとってはうまくいきます:
/* 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;
}
ehandler()
Windows がフル スタックでどのように呼び出すことができるのか疑問に思うかもしれません。sum1to()
ハンドラーが何をすべきかを決定するまで、これらの再帰呼び出しはすべてスタックに残る必要があります。Windows カーネルにはいくつかの魔法があります。スタック オーバーフローが報告されると、ntdll.exe がハンドラーを呼び出せるように、スタックの余分なページもマップします。ハンドラーにブレークポイントを設定すると、gdb でこれを確認できます。私のマシンでは、スタックがアドレス 0x54000 まで成長します。スタック フレームsum1to()
は 0x54000 で停止しますが、例外ハンドラは 0x53000 から 0x54000 までのスタックの余分なページで実行されます。Unix シグナルにはそのような魔法はありません。そのため、Unix プログラムsigaltstack()
はスタック オーバーフローを処理する必要があります。