Skip to main content

IAR Embedded Workbench for Arm 9.70.x

コールフレームの使用の追跡

このセクションの内容:

次のページでは、以下について詳しく説明します。

何を行いますか ?

詳細を参照してください:

コールフレーム情報概要を参照してください。

コールフレーム情報(CFI)はコールフレームの情報です。通常、コールフレームには、リターンアドレス、関数の引数、保存したレジスタ値、一時的なコンパイラ、ローカル変数が含まれます。コールフレーム情報には、2つの重要な機能をサポートするためのコールフレームに関する十分な情報があります。

  • C-SPYは現在の PC(プログラム カウンタ)からコールチェーン全体を再構築して、コールチェーンの各関数のローカル変数値を表示するために、コールフレーム情報に使用できます。この情報は例えばCall Stackウィンドウで使用されます。。

  • アプリケーションのスタック使用量の計算のために、可能な呼び出しの情報とコールフレームの情報を使用できます。この機能はお使いの製品ではサポートされていない場合があります。

コンパイラは、すべてのCおよびC++ソースコードのために、自動的にコールフレーム情報を生成します。コールフレームの情報も通常、システム ライブラリの各アセンブラルーチンのために提供されます。ただし、ほかのアセンブラ ルーチンを持っていて、これらのルーチンを実行するときにC-SPYを有効にしてコールスタックを表示したい場合は、アセンブラの必要なコールフレーム情報のソースコードに注釈を追加する必要があります。スタック使用量もこの方法を使用できます(各関数呼び出しに必要な注釈を追加する方法)が、スタック使用量制御ファイルのルーチンのスタック使用量情報を指定できます(スタック使用解析制御ファイルを参照)。この方法のほうが通常簡単です。

コールフレーム情報の詳細

cfi ディレクティブを使用して、アセンブラ ファイルにコールフレーム情報を追加できます。これらを使用して次のことを指定できます。

  • コールフレームの開始アドレスCFA列 (CFA)として参照されます。コールフレームには2つの種類があります。

    • スタック上ースタックフレーム。スタック フレームにおいて、CFA は通常、ルーチンから戻った後のスタック ポインタの値になります。

    • 静的オーバーレイシステムで使用される静的メモリ内-静的オーバーレイフレーム この種類のコールフレームは、Arm コアでは必要なく、またそのためサポートされていません。

  • リターンアドレスを検索する方法。

  • ルーチンから返す時にレジスタなどの様々なリソースを復元する方法。

各アセンブラモジュールのコールフレーム情報を追加するとき、次のことをする必要があります。

  1. 追跡するリソースを記述しているnames blockを提供します。

  2. 追跡されるリソースを定義するcommon blockを提供して、そのデフォルト値を指定します。この情報はコンパイラが使用する呼び出し規約に関連している必要があります。

  3. ソースコードで使用するリソースを注釈をつけます。これは、コールフレームで実行された変更を説明します。通常、これには、いつスタックポインタが変更されたか、いつ保護レジスタがスタックに待避、復帰したかについての情報が含まれます。

    これを行うには、追跡する各リソースのルールを指定する連続したソースコードの含むdata blockを定義する必要があります。ルールの記述力が十分でない場合は、代わりにCFI式を使用できます。

呼び出し規約に関する詳細記述では、広範なコールフレーム情報を必要とする場合があります。多くの場合は、より限定的なアプローチで十分です。コールフレーム情報を正しく処理するアセンブラ言語ルーチンを作成する良い方法は、アセンブラ出力を生成するためにコンパイルするCスケルトン関数から開始することです。例については、スケルトンコードの作成コンパイラおよびリンカのユーザードキュメントを参照してください。

names blockの定義

names blockは、プロセッサで使用可能なリソースを宣言するために使用します。names blockの内部に、追跡可能なすべてのリソースが定義されています。

names blockの開始と終了には、以下のディレクティブを使用します。

CFI NAMES name
CFI ENDNAMES name

ここで、nameはブロックの名前です。

一度に開けるnames blockは1つだけです。

names blockの内部では、リソース宣言、スタックフレーム宣言、静的オーバーレイフレーム宣言、およびベースアドレス宣言という4種類の宣言を使用できます。

  • リソースを宣言するには、以下のいずれかのディレクティブを使用します。

    CFI RESOURCE resource : bits
    CFI VIRTUALRESOURCE resource : bits

    パラメータは、リソースの名前とリソースのサイズ(ビット単位)です。DWARF for the ARM architecture または DWARF for the Arm 64-bit architecture (AArch64)のいずれかのデバイスアーキテクチャに関連する、AEABIドキュメントで定義したレジスタ名の1つを名前に使用する必要があります。仮想リソースは論理的な概念であり、プロセッサレジスタなどの物理リソースと対比されます。仮想リソースは通常、リターンアドレスに使用します。

    複数のリソースを宣言する場合、リソース間をコンマで区切ります。

    リソースは、2つ以上のパーツから成る複合リソースを使用することもできます。複合リソースの構成を宣言するには、次のようにディレクティブを使用します。

    CFI RESOURCEPARTS resource part, part, …
    

    パートはコンマで区切ります。リソースとそのパートは、上で説明したように、既にリソースとして宣言されている必要があります。

  • スタックフレームCFAを宣言するには、次のようにディレクティブを使用します。

    CFI STACKFRAME cfa resource type
    

    パラメータは、スタックフレームCFAの名前、関連するリソースの名前(スタックポインタ)、メモリタイプ(アドレス空間の取得用)です。複数のスタックフレームCFAを宣言する場合、カンマで区切ります。

    コールスタックに戻る場合、前の関数フレームの正しい値を取得するために、スタックフレームCFAの値が対応するスタックポインタリソースにコピーされます。

  • ベースアドレスCFAを宣言するには、次のようにディレクティブを使用します。

    CFI BASEADDRESS cfa type
    

    パラメータは、CFA の名前とメモリタイプです。複数のベースアドレスCFAを宣言する場合、カンマで区切ります。

    ベースアドレスCFAを使用すると、CFAの取り扱いが簡単になります。スタックフレームCFAと違い、復元する関連スタックポインタリソースはありません。

COMMON ブロックの定義

すべての追跡対象リソースの初期内容を宣言するには、COMMONブロックを使用します。通常、使用される各呼び出し規約にcommon blockが1つずつあります。

common blockを開始するには、以下のディレクティブを使用します。

CFI COMMON name USING namesblock

ここで、nameは新しいブロックの名前であり、namesblockは以前に定義されたNAMEブロックの名前です。

リターンアドレス列を宣言するには、以下のディレクティブを使用します。

CFI RETURNADDRESS resource type

ここでresourcenamesblockに定義されたリソースであり、typeは呼び出し関数を格納するメモリです。COMMONブロックに対してリターンアドレス列を宣言する必要があります。

common blockの内部には、common blockで利用できるディレクティブを使用して、CFAまたはリソースの初期値を宣言できます。COMMONブロックのコールフレーム情報ディレクティブを参照してください。これらのディレクティブの使用方法については、リソースおよびスタックの深さを追跡するための規則を指定するおよび複雑なケースを追跡するためのCFI式の使用を参照してください。

common blockを終了するには、以下のディレクティブを使用します。

CFI ENDCOMMON name

ここで、nameはCOMMONブロックを開始するために使用される名前です。

データブロック内のソースコードに注釈をつける

データブロックには、1つの連続したコードの実際の追跡情報が含まれます。

データブロックを開始するには、以下のディレクティブを使用します。

CFI BLOCK name USING commonblock

ここで、nameは新しいブロックの名前であり、commonblockは以前に定義されたCOMMONブロックの名前です。

現在のデータブロックの当該コードが、定義された関数の一部である場合、次のディレクティブを使用して関数名を指定します。

CFI FUNCTION label

ここで、labelは関数を開始するコードラベルです。

現在のデータブロックの当該コードが関数の一部でない場合、次のディレクティブを使用してこのことを指定します。

CFI NOFUNCTION

データブロックを終了するには、以下のディレクティブを使用します。

CFI ENDBLOCK name

ここで、nameはデータブロックを開始するために使用される名前です。

データブロックの内部では、データブロックに利用できるディレクティブを使用して、リソースの値を操作できます。データブロックのコールフレーム情報ディレクティブを参照してください。これらのディレクティブの使用方法については、リソースおよびスタックの深さを追跡するための規則を指定するおよび複雑なケースを追跡するためのCFI式の使用を参照してください。

リソースおよびスタックの深さを追跡するための規則を指定する

個々のリソースの追跡情報を記述するために、特別な構文の2セットの簡易規則が用意されています。

  • リソース追跡のための規則

    CFI resource { UNDEFINED | SAMEVALUE | CONCAT }

    CFI resource { resource | FRAME(cfa, offset) }

  • スタックの深さを追跡するための規則 (CFA)

    CFI cfa { NOTUSED | USED }

    CFI cfa { resource | resource + constant | resource - constant }

これらの規則は、common block 内で使用してリソースとCFAの初期情報を記述したり、data block内で使用してリソースとCFAの情報への変更を記述したりすることができます。

簡易規則で十分に記述できない場合には、専用の演算子を使用した完全なCFI 式を使用して情報を記述できます。複雑なケースを追跡するためのCFI式の使用を参照してください。ただし、可能な限り、CFI式ではなく規則を使用してください。

リソース追跡のための規則

コールフレームを1つ戻る場合にリソースがどこにあるのかを概念的に記述するリソース用規則です。このため、CFIディレクティブでリソース名の後にあるアイテムを、リソースのロケーションと呼びます。

追跡対象のリソースが復元されている、言い換えればこのリソースの場所が既に正しく認識されていることを宣言するには、ロケーションにSAMEVALUEを使用します。リソースには既に正しい値が含まれているので、概念的には、これによってリソースの復元が不要であることが宣言されます。たとえば、レジスタR11が同じ値に復元されることを宣言するには、以下のディレクティブを使用します。

CFI R11 SAMEVALUE

リソースが追跡対象ではないことを宣言するには、ロケーションとしてUNDEFINEDを使用します。リソースは追跡されないため、概念的には、これによって(コールフレームを1つ戻る場合に)リソースの復元が不要であることが宣言されます。これを使用して意味があるのは、リソースの初期ロケーションを宣言する場合のみです。例えばR11スクラッチレジスタであり復元不要であることを宣言するには、以下のディレクティブを使用します。

CFI R11 UNDEFINED

リソースが一時的に他のリソースに格納されていることを宣言するには、ロケーションとしてリソース名を使用します。たとえば、レジスタR11が一時的にレジスタR12に格納されており、そのレジスタから復元する必要があることを宣言するには、以下のディレクティブを使用します。

CFI R11 R12

リソースが現在、スタック内のどこかに存在することを宣言するには、FRAME(cfaoffset)をリソースのロケーションとして使用します。ここで、cfaは「フレームポインタ」として使用されるCFA識別子であり、offsetはCFAに対して相対的なオフセットです。たとえば、レジスタR11 フレームポインタCFA_SPから数えてオフセット-4に存在することを宣言するには、以下のディレクティブを使用します。

CFI R11 FRAME(CFA_SP,-4)

複合リソースには、追加ロケーションがもう1つあります。これはCONCATで、複合リソースのリソースパートを結合するとリソースのロケーションを検出できることを宣言します。たとえば、リソースパートRETLORETHIから成る複合リソースRETを考えてみます。リソースパートを検証して連結するとRETの値を検出できることを宣言するには、以下のディレクティブを使用します。

CFI RET CONCAT

このためには、リソースパーツの少なくとも1つに、前述の規則を使用する定義が必要です。

スタックの深さを追跡するための規則 (CFA)

リソース用の規則と違い、CFA用の規則にはコールフレームの先頭のアドレスを記述します。コールフレームには、アセンブラ呼び出し命令によってプッシュされるリターンアドレスが含まれる場合があります。CFA規則は、現在のスタックフレームの先頭のアドレスを計算する方法を記述します。

各スタックフレームCFAには、スタックポインタが関連付けられています。コールフレームを1つ戻ると、関連スタックポインタは現在のCFAで復元されます。スタックフレームCFAには、リソース(スタックフレームCFAに関連付けられたリソースとは限らない)からのオフセット、またはNOTUSEDという2種類の規則があります。

CFAを使用せず、関連するスタックポインタは通常のリソースとして追跡する必要があることを宣言するには、CFAのアドレスとしてNOTUSEDを使用します。たとえば、CFA_SPという名前のCFAをこのコードブロックで使用しないことを宣言するには、以下のディレクティブを使用します。

CFI CFA_SP NOTUSED

CFAのアドレスが、スタックポインタの値に相対的なオフセットであることを宣言するには、リソースとオフセットを指定します。たとえば、SPというリソースの値に4を足すとCFA_SPという名前のCFAを取得できることを宣言するには、以下のディレクティブを使用します。

CFI CFA_SP SP + 4

複雑なケースを追跡するためのCFI式の使用

リソースとCFA用の簡易規則では十分に記述できない場合には、コールフレーム情報式 (CFI式)を使用できます。ただし、可能な限り、簡易規則を使用するようにしてください。

CFI式は、オペランドと演算子から構成されています。CFI 式には3セットの演算子が使用できます。

  • 単項演算子

  • 2項演算子

  • 3項演算子

ほぼ、通常のアセンブラ式と同じ演算子を使用できます。

この例には、R12がその元の値に格納されます。ただし、それを保存する代わりに、2 つのインクリメント後の効果は、影響が減算命令により未実行となります。

AddTwo:
        cfi block addTwoBlock using myCommon
        cfi function addTwo
        cfi nocalls
        cfi r12 samevalue
        add @r12+, r13
        cfi r12 sub(r12, 2)
        add @r12+, r13
        cfi r12 sub(r12, 4)
        sub #4, r12
        cfi r12 samevalue
        ret
        cfi endblock addTwoBlock

CFI式の演算子の使用のための構文については、リソースやCFAを追跡するためのコールフレーム情報ディレクティブを参照してください。

スタック使用量解析ディレクティブ

スタック使用量解析ディレクティブ(CFI FUNCALLCFI TAILCALLCFI INDIRECTCALL、およびCFI NOCALLS)は、スタック使用量解析に必要なコールグラフを構築するために使用されます。これらは、データブロック内でのみ使用できます。データブロックが関数ブロックの場合(つまり、CFI FUNCTIONディレクティブがデータブロック内で使用されている場合)、callerパラメータを指定しないでください。スタック使用量解析ディレクティブが関数同士で共有されているコード内で使用されている場合、情報が適用される可能性のある関数を指定するときにはcallerパラメータを使用する必要があります。

呼び出しを実行する命令の前にCFI FUNCALLCFI TAILCALLCFI INDIRECTCALL ディレクティブはすぐに配置する必要があります。CFI NOCALLSディレクティブは、データブロックのどこにでも配置できます。

CFI ディレクティブの使用例

以下はArmコアに固有の例です。その他の例は、Cソースファイルをコンパイルするときに、アセンブラ出力を生成すれば入手できます。

スタックポインタR13、リンクレジスタR14、および汎用レジスタR0-R12を備えたCortex-M3 デバイスについて考えてみます。レジスタR0R2R3、およびR12はスクラッチレジスタ(これらのレジスタは関数の呼出しによって破棄されることがあります)として使用されるのに対して、レジスタR1は関数呼出しの後に復元する必要があります。

以下の短いソースコードと、対応するコールフレームの情報を考えてみましょう。開始時点で、レジスタR14に32ビットのリターンアドレスが含まれているとします。スタックは上位アドレスからゼロに向かって大きくなります。CFAはコールフレームのトップを指定します。つまり、関数から戻ったときのスタックポインタの値です。

アドレス

CFA

R1

R4-R11

R14

R0、R2、R3、R12

アセンブラコード

00000000

R13 + 0

SAME

SAME

SAME

Undefined

PUSH {r1,lr}

00000002

R13 + 8

CFA - 8

CFA - 4

MOVS r1,#4

00000004

BL func2

00000008

POP {r0,lr}

0000000C

R13 + 0

R0

SAME

MOV r1,r0

0000000E

SAME

BX lr

表143 バックトレース行と列付きのサンプルコード 


各行は、命令を実行するの追跡対象リソースの状態を示します。たとえば、MOV R1,R0命令では、R1レジスタの元の値はR0レジスタにあり、関数フレーム(CFA列)のトップはR13 + 0です。アドレス0000の行は最初の行であり、関数に使用された呼び出し規約の結果です。

R14 列はリターンアドレスの列です。つまり、リターンアドレスのロケーションです。R1列の最初の行はSAMEです。これは、R1レジスタの値が、既知の値と同じ値に復元されることを示します。関数からの終了に復元される必要がないので、定義されてないレジスタがいくつかあります。

NAMEブロックの定義

上の例で指定するNAMEブロックは次のようになります。

            cfi     names ArmCore
            cfi     stackframe cfa r13 DATA
            cfi     resource r0:32,  r1:32,  r2:32,  r3:32
            cfi     resource r4:32,  r5:32,  r6:32,  r7:32
            cfi     resource r8:32,  r9:32,  r10:32, r11:32
            cfi     resource r12:32, r13:32, r14:32
            cfi     endnames ArmCore
COMMONブロックの定義
            cfi     common trivialCommon using ArmCore
            cfi     codealign 2
            cfi     dataalign 4
            cfi     returnaddress r14 CODE
            cfi     cfa     r13+0
            cfi     default samevalue
            cfi     r0      undefined
            cfi     r2      undefined
            cfi     r3      undefined
            cfi     r12     undefined
            cfi     endcommon trivialCommon

注記

CFAとリソースが関連しているので、R13CFI ディレクティブを使用して変更できません。

データブロックの定義

CFIディレクティブは、バックトレース情報が変更された地点に配置してください。つまり、バックトレース情報を変更した命令の直後ということです。

            section MYCODE:CODE(2)
 
            cfi     block trivialBlock using trivialCommon
            cfi     function func1

            thumb

func1       push    {r1,lr}
        
            cfi     r1  frame(cfa, -8)
            cfi     r14 frame(cfa, -4)
            cfi     cfa r13+8

            movs    r1,#4

            cfi     funcall func2

            bl      func2
            pop     {r0,lr}

            cfi     r1  r0
            cfi     r14 samevalue
            cfi     cfa r13

            mov     r1,r0


            cfi     r1 samevalue

            bx      lr

            cfi     endblock trivialBlock

            end