コールフレームの使用の追跡
次のページでは、以下について詳しく説明します。
何を行いますか ?
詳細を参照してください:
コールフレーム情報概要を参照してください。
コールフレーム情報(CFI)はコールフレームの情報です。通常、コールフレームには、リターンアドレス、関数の引数、保存したレジスタ値、一時的なコンパイラ、ローカル変数が含まれます。コールフレーム情報には、2つの重要な機能をサポートするためのコールフレームに関する十分な情報があります。
C-SPYは現在の
PC(プログラム カウンタ)からコールチェーン全体を再構築して、コールチェーンの各関数のローカル変数値を表示するために、コールフレーム情報に使用できます。この情報は例えばCall Stackウィンドウで使用されます。。アプリケーションのスタック使用量の計算のために、可能な呼び出しの情報とコールフレームの情報を使用できます。この機能はお使いの製品ではサポートされていない場合があります。
コンパイラは、すべてのCおよびC++ソースコードのために、自動的にコールフレーム情報を生成します。コールフレームの情報も通常、システム ライブラリの各アセンブラルーチンのために提供されます。ただし、ほかのアセンブラ ルーチンを持っていて、これらのルーチンを実行するときにC-SPYを有効にしてコールスタックを表示したい場合は、アセンブラの必要なコールフレーム情報のソースコードに注釈を追加する必要があります。スタック使用量もこの方法を使用できます(各関数呼び出しに必要な注釈を追加する方法)が、スタック使用量制御ファイルのルーチンのスタック使用量情報を指定できます(スタック使用解析制御ファイルを参照)。この方法のほうが通常簡単です。
コールフレーム情報の詳細
cfi ディレクティブを使用して、アセンブラ ファイルにコールフレーム情報を追加できます。これらを使用して次のことを指定できます。
コールフレームの開始アドレスはCFA列 (CFA)として参照されます。コールフレームには2つの種類があります。
スタック上ースタックフレーム。スタック フレームにおいて、CFA は通常、ルーチンから戻った後のスタック ポインタの値になります。
静的オーバーレイシステムで使用される静的メモリ内-静的オーバーレイフレーム この種類のコールフレームは、Arm コアでは必要なく、またそのためサポートされていません。
リターンアドレスを検索する方法。
ルーチンから返す時にレジスタなどの様々なリソースを復元する方法。
各アセンブラモジュールのコールフレーム情報を追加するとき、次のことをする必要があります。
呼び出し規約に関する詳細記述では、広範なコールフレーム情報を必要とする場合があります。多くの場合は、より限定的なアプローチで十分です。コールフレーム情報を正しく処理するアセンブラ言語ルーチンを作成する良い方法は、アセンブラ出力を生成するためにコンパイルするCスケルトン関数から開始することです。例については、スケルトンコードの作成コンパイラおよびリンカのユーザードキュメントを参照してください。
names blockの定義
names blockは、プロセッサで使用可能なリソースを宣言するために使用します。names blockの内部に、追跡可能なすべてのリソースが定義されています。
names blockの開始と終了には、以下のディレクティブを使用します。
CFI NAMESnameCFI ENDNAMESname
ここで、nameはブロックの名前です。
一度に開けるnames blockは1つだけです。
names blockの内部では、リソース宣言、スタックフレーム宣言、静的オーバーレイフレーム宣言、およびベースアドレス宣言という4種類の宣言を使用できます。
リソースを宣言するには、以下のいずれかのディレクティブを使用します。
CFI RESOURCEresource:bitsCFI VIRTUALRESOURCEresource:bitsパラメータは、リソースの名前とリソースのサイズ(ビット単位)です。DWARF for the ARM architecture または DWARF for the Arm 64-bit architecture (AArch64)のいずれかのデバイスアーキテクチャに関連する、AEABIドキュメントで定義したレジスタ名の1つを名前に使用する必要があります。仮想リソースは論理的な概念であり、プロセッサレジスタなどの物理リソースと対比されます。仮想リソースは通常、リターンアドレスに使用します。
複数のリソースを宣言する場合、リソース間をコンマで区切ります。
リソースは、2つ以上のパーツから成る複合リソースを使用することもできます。複合リソースの構成を宣言するには、次のようにディレクティブを使用します。
CFI RESOURCEPARTS
resourcepart, 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 COMMONnameUSINGnamesblock
ここで、nameは新しいブロックの名前であり、namesblockは以前に定義されたNAMEブロックの名前です。
リターンアドレス列を宣言するには、以下のディレクティブを使用します。
CFI RETURNADDRESSresourcetype
ここでresourceはnamesblockに定義されたリソースであり、typeは呼び出し関数を格納するメモリです。COMMONブロックに対してリターンアドレス列を宣言する必要があります。
common blockの内部には、common blockで利用できるディレクティブを使用して、CFAまたはリソースの初期値を宣言できます。COMMONブロックのコールフレーム情報ディレクティブを参照してください。これらのディレクティブの使用方法については、リソースおよびスタックの深さを追跡するための規則を指定するおよび複雑なケースを追跡するためのCFI式の使用を参照してください。
common blockを終了するには、以下のディレクティブを使用します。
CFI ENDCOMMON nameここで、nameはCOMMONブロックを開始するために使用される名前です。
データブロック内のソースコードに注釈をつける
データブロックには、1つの連続したコードの実際の追跡情報が含まれます。
データブロックを開始するには、以下のディレクティブを使用します。
CFI BLOCKnameUSINGcommonblock
ここで、nameは新しいブロックの名前であり、commonblockは以前に定義されたCOMMONブロックの名前です。
現在のデータブロックの当該コードが、定義された関数の一部である場合、次のディレクティブを使用して関数名を指定します。
CFI FUNCTION labelここで、labelは関数を開始するコードラベルです。
現在のデータブロックの当該コードが関数の一部でない場合、次のディレクティブを使用してこのことを指定します。
CFI NOFUNCTION
データブロックを終了するには、以下のディレクティブを使用します。
CFI ENDBLOCK nameここで、nameはデータブロックを開始するために使用される名前です。
データブロックの内部では、データブロックに利用できるディレクティブを使用して、リソースの値を操作できます。データブロックのコールフレーム情報ディレクティブを参照してください。これらのディレクティブの使用方法については、リソースおよびスタックの深さを追跡するための規則を指定するおよび複雑なケースを追跡するためのCFI式の使用を参照してください。
リソースおよびスタックの深さを追跡するための規則を指定する
個々のリソースの追跡情報を記述するために、特別な構文の2セットの簡易規則が用意されています。
リソース追跡のための規則
CFIresource{ UNDEFINED | SAMEVALUE | CONCAT }CFIresource{resource| FRAME(cfa,offset) }スタックの深さを追跡するための規則 (CFA)
CFIcfa{ NOTUSED | USED }CFIcfa{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(cfa、offset)をリソースのロケーションとして使用します。ここで、cfaは「フレームポインタ」として使用されるCFA識別子であり、offsetはCFAに対して相対的なオフセットです。たとえば、レジスタR11 フレームポインタCFA_SPから数えてオフセット-4に存在することを宣言するには、以下のディレクティブを使用します。
CFI R11 FRAME(CFA_SP,-4)
複合リソースには、追加ロケーションがもう1つあります。これはCONCATで、複合リソースのリソースパートを結合するとリソースのロケーションを検出できることを宣言します。たとえば、リソースパートRETLOとRETHIから成る複合リソース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 addTwoBlockCFI式の演算子の使用のための構文については、リソースやCFAを追跡するためのコールフレーム情報ディレクティブを参照してください。
スタック使用量解析ディレクティブ
スタック使用量解析ディレクティブ(CFI FUNCALL、CFI TAILCALL、CFI INDIRECTCALL、およびCFI NOCALLS)は、スタック使用量解析に必要なコールグラフを構築するために使用されます。これらは、データブロック内でのみ使用できます。データブロックが関数ブロックの場合(つまり、CFI FUNCTIONディレクティブがデータブロック内で使用されている場合)、callerパラメータを指定しないでください。スタック使用量解析ディレクティブが関数同士で共有されているコード内で使用されている場合、情報が適用される可能性のある関数を指定するときにはcallerパラメータを使用する必要があります。
呼び出しを実行する命令の前にCFI FUNCALL、CFI TAILCALL、CFI INDIRECTCALL ディレクティブはすぐに配置する必要があります。CFI NOCALLSディレクティブは、データブロックのどこにでも配置できます。
CFI ディレクティブの使用例
以下はArmコアに固有の例です。その他の例は、Cソースファイルをコンパイルするときに、アセンブラ出力を生成すれば入手できます。
スタックポインタR13、リンクレジスタR14、および汎用レジスタR0-R12を備えたCortex-M3 デバイスについて考えてみます。レジスタR0、R2、R3、およびR12はスクラッチレジスタ(これらのレジスタは関数の呼出しによって破棄されることがあります)として使用されるのに対して、レジスタR1は関数呼出しの後に復元する必要があります。
以下の短いソースコードと、対応するコールフレームの情報を考えてみましょう。開始時点で、レジスタR14に32ビットのリターンアドレスが含まれているとします。スタックは上位アドレスからゼロに向かって大きくなります。CFAはコールフレームのトップを指定します。つまり、関数から戻ったときのスタックポインタの値です。
アドレス | CFA | R1 | R4-R11 | R14 | R0、R2、R3、R12 | アセンブラコード |
|---|---|---|---|---|---|---|
| R13 + 0 | SAME | SAME | SAME | Undefined |
|
| R13 + 8 | CFA - 8 | CFA - 4 |
| ||
|
| |||||
|
| |||||
| R13 + 0 | R0 | SAME |
| ||
| SAME |
|
各行は、命令を実行する前の追跡対象リソースの状態を示します。たとえば、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 ArmCoreCOMMONブロックの定義
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