3

IDT を介してカーネル割り込みを処理しようとしています。Linux で Intel x86 を使用しています。

IDT と割り込みエントリを設定し、いくつかのテストを開始して割り込みハンドラを表示しました。

試してみるとint $0x0、完全に機能します。ハンドラーが呼び出されますが、エラーコードがプッシュされた状態で例外を試すと、無限ループに入ります。

スキーマは次のとおりです。

例外が到着すると、ハンドラーの最初の部分は ASM にあり、共通の C 部分を呼び出します。

my_handler.c

void handler(int i)
{
  printf("Exception %d caught\n", i);
}

my_handlers.S

common:
    pushal

    pushl %ds
    pushl %es
    pushl %fs
    pushl %gs

    addl $48, %esp                  // 4 4-bytes segments pushed
                                    // + 8 4-bytes registers (pushal)
`                                   // esp points on exception code

    call handler                    // call the C handler with exception code

    subl $48, %esp

    popl %gs
    popl %fs
    popl %es
    popl %ds

    popal

    addl $8, %esp                   // 4-byte error code + 4-byte exception number
    iret


exception_de_handler:
    pushl $0                        // Fake error code
    pushl $0                        // interrupt number
    jmp common

exception_gp_handler:
    // error code is pushed by µproc.
    pushl $13                       // interrupt number
    jmp common

exception_pf_handler:
    // error code is pushed by µproc.
    pushl $14                       // interrupt number
    jmp common

次のコードを実行しようとすると:

int* a = 0x0;
*a = 42;

動作し、実行が再開されます*a = 42;

しかし、私が試してみると:

int* a = 0x0;
*a = 42;
*a = 1337;

それは無限ループに入ります:

Exception 14 caught
Exception 13 caught
Exception 13 caught
Exception 13 caught
Exception 13 caught
        .....
Exception 13 caught
Exception 13 caught
Exception 13 caught
        .....

最初の例外 Page Fault(14) が処理され、次に General Protection(13) でループされるのはなぜですか?

回答ありがとうございます。

4

1 に答える 1

3

スタックを台無しにしていると思います。割り込みハンドラでスタックをどうするかについては、非常に注意する必要があります。この場合、次のことを行うようです:-

エラー コードのプッシュ (CPU で実行可能) レジスタのプッシュ セグメント レジスタのプッシュ

スタック ポインターに 0x48 を追加して、スタックを完全に巻き戻して、エラー コードを指すようにします。

C関数を呼び出す

これが実際に行っていることは、セグメントレジスタが格納されていたスタックの部分を「解放」することです。実際、リターンアドレスがスタックにプッシュされるため、C 関数について心配する必要さえありません。命令を呼び出し、C 呼び出しに到達する前に ds と es の記録を吹き飛ばします。C 呼び出しから戻ると、呼び出しスタックを整理しようとしますが、完全に正しく処理されません。これは、一部は既に台無しにしてしまっているため、一部は呼び出し後にスタックをクリーンアップしていないためです。関数呼び出し (ハンドラーが _cdecl 呼び出し規則を使用すると仮定)。

これにより、ds の偽の値がポップされます。それを ds にロードすると、CPU は値を GDT と照合してチェックし、それが無効であることを検出します。この時点で、あなたが見ているGPF(例外13)が発生します。これにより、ある程度までスタックが回復し (CPU が SS を処理します)、古い値の ds セットが残されます。そのため、実際に ds を変更することはなく、printf を再度実行できます。

スタックの位置合わせにはもっと注意する必要があります。また、スタック ポインターに追加するたびに、その範囲内にあったデータが永久に失われることを考慮する必要があります。あなたをつまずかせるつもりです。

于 2012-02-14T23:01:50.710 に答える