Skip to main content

IAR Embedded Workbench for Arm 9.70.x

64ビットモードの例外関数

このセクションの内容:

コンパイラは、64ビットモードの例外関数の書き込みに関連する基本関数を提供します。

  • 拡張キーワード__exception__nested、および__svc

  • 組み込み関数__enable_interruptおよび__disable_interrupt

  • 特別な関数の名前Synchronous_Handler_A64Error_Handler_A64IRQ_Handler_A64、およびFIQ_Handler_A64

例外関数

例外関数は外部の割り込みイベントまたは内部例外を処理するために使用されます。例外が発生すると、コアの実行されたコードが停止され例外のコードが実行し始めます。例外が処理された後、例外の対象となったコードの環境が復元されることが重要です。これはプロセッサレジスタ、ステータスレジスタなどの値が含まれます。実行は、例外が発生しなかったように続行できます。

__exceptionは関数タイプ属性で、例外関数を定義します。リターン値にvoidが必要で、パラメーターを持つことはできません。使用したレジスタはすべて入口で保存され、出口でに復元されます。ERET命令で返します。

__exception void func(void)
{
    /* Do something */
}

例外およびC++メンバ関数

静的メンバ関数だけが例外関数になります。非静的メンバ関数を呼び出す際には、オブジェクトに割当てる必要があります。例外が発生し、例外関数が呼び出されるときは、メンバ関数の割り当てに使用可能なオブジェクトは存在しません。

例外ベクタテーブル

IAR C/C++ コンパイラは、3つの例外レベルEL1、EL2、およびEL3の同じ例外ベクタテーブルを使用します。例外ベクタテーブルはリンカが定義したシンボル__eevectorから開始します。それには16ベクタあり、1つのベクタは128バイトです。

IAR C/C++ コンパイラは、例外レベルを変更せず、または現在の例外レベル(オフセット0x2000x2800x300、および0x380)にSPを使用する、ベクタのみを定義します。それらの4つの定義済みベクタの名前は、 Synchronous_Handler_A64Error_Handler_A64IRQ_Handler_A64、およびFIQ_Handler_A64です。これらにはデフォルトの実装があり、それらの名前のいずれかを使用して __exception 関数を定義することでオーバーライドできます。関数がベクタに大きすぎて入らない場合は、コンパイラはエラーを発行します。この関数は直接例外関数として使用できません。代わりにしなければならないこと:

  1. eeなどのグローバルシンボルで始まる、アセンブラモジュールを書き込みます。シンボルは例外関数にジャンプします。

  2. リンカ設定ファイルを編集します。Synchronous_Handler_A64などの関連する例外関数のplace atディレクティブをplace at address synchronous_evector { symbol ee };で置き換えます。

デフォルトでは、例外ベクタテーブルはアドレス2048に配置されます。 別のアドレスにそれを配置するには、以下のいずれかの方法を使用します。

  • リンカオプション--config_defを使用してリンカ設定シンボル__Exception_table_addressを次のように設定します。--config_def __Exception_table_address=4096

  • プロジェクトが使用する、リンカ設定ファイルを編集します。

例外テーブルは2KBでアラインメントされています。

ネストされた例外関数

例外関数はネストされます。 これは入口でELR_EL1システムレジスタも保存します。関数が終了すると、割り込みは無効になり保存したレジスタはすべてリストアされます。

SPSR_EL1システムレジスタは自動的に保存されません。例外後にステータスフラグを保持するには、割り込みを有効にする前に明示的に保存する必要があります。これを明示的に行うと、SPSR_EL1の別のビットを操作できるようになります。

例:

#include <intrinsics.h>
__exception __nested void func(void)
{
    // All used registers + ELR_EL1 have been saved. SPSR_EL1
    // and ESR_EL1 can be saved/used.

    __enable_interrupt();

    // Do stuff

    __disable_interrupt();

    // The possibly changed SPSR_EL1 and ESR_EL1 can be restored.
    // At exit, interrupts will be disabled and then all used
    // registers are restored. Then ERET is executed.
}

Supervisor-defined関数

関数型属性 __svcを使用して定義された関数は戻り値を持ち、パラメータを受け取ることができます。同じレジスタを通常の関数呼び出しとして保存し、ERET命令で返します。SVC定義の関数は同期例外を処理します。

__svc void func(void)
{
    /* Do something */
}

次の例を参照してください。

Supervisor call

SVCはA64命令で、supervisorを呼び出します。これは例外です。それは同期例外ベクタで処理されます。IAR C/C++ コンパイラは、関数宣言または定義の前にプラグマディレクティブsvc_numberを使用することで、SVC命令で関数の呼出しに使用する、通常の呼出し命令の交換をサポートしています。提供された番号はESR_EL1システムレジスタに格納されます。

#pragma svc_number = 23
__svc int Synchronous_Handler_A64(int i)
{
    return i;
}
    
void f()
{
    int i = Synchronous_Handler_A64(5); // Will use an SVC
}

SVC関数の想定した使用は、より高い例外レベルのより低い例外レベル呼び出しコードでコードを実行することです。

// User code
#pragma svc_number = 1
int svc1(int);
#pragma svc_number = 2
int svc2(int);

int main(void)
{
    svc1(1);
}

// Supervisor code
__svc int Synchronous_Handler_A64(int a)
{
    // Get syndrome: AARCH64 SVC
    long long nr = 0;
    __asm("MRS %x0, ESR_EL1\n" : "=r"(nr));
    int ec = (nr >> 26) & 0x3F;
    if (ec != 0x15)
        return -1;

    // Get SVC number.
    nr &= 0xFF'FFFF;

    return nr + a;
}

#pragma svc_numberで宣言した関数は、同じ関数シグネチャを使用する必要はありません。異なるシグネチャを使用する場合、パラメータを渡して戻り値を正しく処理するために、Synchronous_Handler_A64 をさまざまな呼び出しハンドラへのトランポリンとしてアセンブラ言語で記述する必要があります。

リセットアドレス

デフォルトではリセットアドレスはアドレス0と想定されています。別のアドレスにそれを配置するには、以下のいずれかの方法を使用します。

  • リンカオプション--config_defを使用してリンカ設定シンボル__Reset_addressを次のように設定します。--config_def __Reset_address=4096

  • プロジェクトが使用する、リンカ設定ファイルを編集します。