web-dev-qa-db-ja.com

ARMのSP(スタック)およびLRとは何ですか?

定義を繰り返し読んでいますが、ARMのSPとLRが何であるかまだわかりません。 PC(次の命令のアドレスを表示)を理解しています。SPとLRはおそらく似ていますが、それが何なのかわかりません。手伝っていただけませんか?

編集:例で説明できるなら、それはすばらしいでしょう。

編集:最終的にLRの目的がわかりましたが、SPの目的はまだわかりません。

68
good_evening

LRは リンクレジスタ 関数呼び出しの戻りアドレスを保持するために使用されます。

SPはスタックポインターです。スタックは通常、関数呼び出し全体で「自動」変数とコンテキスト/パラメーターを保持するために使用されます。概念的には、「スタック」をデータを「積み重ねる」場所と考えることができます。あるデータを別のデータの上に「積み重ね」ていくと、スタックポインターは、データの「スタック」がどれだけ「高い」かを示します。 「スタック」の「上部」からデータを削除し、短くすることができます。

ARMアーキテクチャリファレンスから:

SP、スタックポインター

レジスタR13は、アクティブスタックへのポインタとして使用されます。

Thumbコードでは、ほとんどの命令がSPにアクセスできません。 SPにアクセスできる唯一の命令は、SPをスタックポインターとして使用するように設計された命令です。スタックポインターとして以外の目的でSPを使用することは非推奨です。注スタックポインターとして以外の目的でSPを使用すると、オペレーティングシステム、デバッガー、およびその他のソフトウェアシステムの要件に違反し、誤動作を引き起こす可能性があります。

LR、リンクレジスタ

レジスタR14は、サブルーチンからの戻りアドレスを格納するために使用されます。また、LRを他の目的に使用することもできます。

BLまたはBLX命令がサブルーチン呼び出しを実行すると、LRがサブルーチンの戻りアドレスに設定されます。サブルーチンの戻りを実行するには、LRをプログラムカウンターにコピーして戻します。これは通常、BLまたはBLX命令でサブルーチンに入った後、次の2つの方法のいずれかで行われます。

•BX LR命令で戻ります。

•サブルーチンエントリで、次の形式の命令を使用してLRをスタックに保存します:{、LR}をプッシュし、一致する命令を使用してPOP {、PC}を返します...

このリンクは簡単なサブルーチンの例を示しています。

呼び出し前にスタックにレジスタを保存し、ポップしてコンテンツを復元する方法の例を次に示します。

84
Guy Sirton

SPは、r13と入力するためのショートカットであるスタックレジスタです。 LRは、r14のショートカットを登録するリンクです。 PCはr15を入力するためのショートカットプログラムカウンターです。

分岐リンク命令blと呼ばれる呼び出しを実行すると、戻りアドレスはリンクレジスタr14に配置されます。プログラムカウンターpcは、分岐先のアドレスに変更されます。

従来のARMコアにはいくつかのスタックポインターがあります(cortex-mシリーズは例外です)。たとえば、フォアグラウンドで実行しているときとは異なるスタックを使用している場合、割り込みは発生しません。コードを変更するには、通常どおりspまたはr13を使用します。ハードウェアが切り替えを行い、命令をデコードするときに正しいコードを使用します。

従来のARM命令セット(親指ではありません)を使用すると、スタックを下位アドレスから上位アドレスに拡張したり、上位アドレスから下位アドレスに拡張したりするときに自由に使用できます。コンパイラとほとんどの人はスタックポインターを高く設定し、高いアドレスから低いアドレスに下げます。たとえば、0x20000000から0x20008000までのRAMがあり、リンカースクリプトを設定してプログラムをビルドし、0x20000000を実行/使用し、スタートアップコードでスタックポインターを0x20008000に設定します。少なくともシステム/ユーザースタックポインターは、分割する必要があります。必要に応じて他のスタックのメモリを使用します。

スタックは単なるメモリです。プロセッサには通常、PCベースとスタックベースの特別なメモリ読み取り/書き込み命令があります。スタックのものは、通常、PushとPopという名前になっていますが、そうである必要はありません(従来の腕の指示のように)。

http://github.com/lsasim にアクセスすると、ティーチングプロセッサが作成され、アセンブリ言語のチュートリアルがあります。そこのどこかで、スタックに関する議論をします。それはアームプロセッサではありませんが、ストーリーは同じで、アームまたは他のほとんどのプロセッサで理解しようとしているものに直接変換する必要があります。

たとえば、プログラムで必要な変数が20個あるが、16個のレジスタから、少なくとも3つの特別な目的の変数(sp、lr、pc)を引いたとします。いくつかの変数をRAMに保持する必要があります。 r5は頻繁に使用する変数を保持しているので、ramに保持したくないが、何かを行うために別のレジスタが本当に必要なコードのセクションがあり、r5が使用されていない場合、r5を保存できますr5を他の何かに再利用しながら最小限の労力でスタックを作成し、後で簡単に復元できます。

従来の(最初に戻るとは限りません)arm構文:

...
stmdb r13!,{r5}
...temporarily use r5 for something else...
ldmia r13!,{r5}
...

stmは複数のストアであり、一度に複数のレジスターを保存でき、それらすべてを1つの命令で保存できます。

dbは、前のデクリメントを意味します。これは、高アドレスから低アドレスへのスタックの下降です。

ここでr13またはspを使用して、スタックポインターを示すことができます。この特定の命令は、スタック操作に限定されず、他の目的に使用できます。

!は、完了後にr13レジスタを新しいアドレスで更新することを意味します。ここでも、stmは非スタック操作に使用できるため、ベースアドレスレジスタを変更したくない場合があります。その場合はオフです。

次に、括弧{}に、保存するレジスタをコンマ区切りでリストします。

ldmiaは逆で、ldmは複数ロードを意味します。 iaは後の増分を意味し、残りはstmと同じです

したがって、スタックポインターが0x20008000にあった場合、リストに32ビットレジスタが1つあるのでstmdb命令を参照すると、r13の値を使用する前にデクリメントされます。 r13の0x20007FFC。後で、ldmia命令に到達したときにバグがなく、r13に0x20007FFCが含まれていると仮定すると、リストr5に1つのレジスタがあります。したがって、0x20007FFCでメモリを読み取り、その値をr5に入れます。iaはインクリメントを意味し、0x20007FFCは1つのレジスタサイズを0x20008000にインクリメントし、!その番号をr13に書き込んで命令を完了することを意味します。

なぜ固定メモリの場所ではなくスタックを使用するのですか?上記の美しさは、そのコードまたは0x20002000または何でも実行するとr13が0x20007654になる可能性がある場所であり、そのコードがループまたは再帰で機能し、各レベルで機能する場合はさらに良いことです再帰の場合、r5の新しいコピーを保存すると、そのループ内のどこにいるかによって、30個のコピーが保存される場合があります。展開すると、必要に応じてすべてのコピーが戻されます。動作しない単一の固定メモリ位置で。これは、例としてCコードに直接変換されます。

void myfun ( void )
{
   int somedata;
}

変数somedataがスタックに存在するようなCプログラムでは、myfunを再帰的に呼び出した場合、再帰の深さに応じてsomedataの値のコピーが複数作成されます。また、その変数は関数内でのみ使用され、他の場所では必要ないため、その関数内のバイトのみが必要なプログラムの寿命の間、その変数のシステムメモリの量を燃やしたり、その機能ではありません。それがスタックの用途です。

グローバル変数はスタック上に見つかりません。

戻る...

Myfun関数を呼び出すときにコード/関数を使用している関数を実装して呼び出したいとします。 myfun関数は、何かを操作しているときにr5とr6を使用したいのですが、誰かがr5とr6を使用して呼び出したものを破棄したくないので、myfun()の間、これらのレジスタをスタックに保存します。同様に、ブランチリンク命令(bl)とリンクレジスタlr(r14)を調べると、リンクレジスタは1つしかありません。関数から関数を呼び出す場合、呼び出しごとにリンクレジスタを保存する必要があります。 。

...
bl myfun
    <--- the return from my fun returns here
...


myfun:
stmdb sp!,{r5,r6,lr}
sub sp,#4 <--- make room for the somedata variable
...
some code here that uses r5 and r6
bl more_fun <-- this modifies lr, if we didnt save lr we wouldnt be able to return from myfun
   <---- more_fun() returns here
...
add sp,#4 <-- take back the stack memory we allocated for the somedata variable
ldmia sp!,{r5,r6,lr}
mov pc,lr <---- return to whomever called myfun.

スタックの使用状況とリンクレジスタの両方を確認できることを願っています。他のプロセッサは同じ種類のことを異なる方法で行います。たとえば、いくつかは戻り値をスタックに配置し、戻り関数を実行すると、スタックから値を取得してどこに戻るかを認識します。コンパイラC/C++などには、通常、「呼び出し規約」またはアプリケーションインターフェイスがあります(ABIおよびEABIは、ARMが定義したものの名前です)。すべての関数が呼び出し規則に従う場合、呼び出される関数に渡すパラメーターを、規則ごとに正しいレジスタまたはスタックに配置します。そして、各関数は、内容を保存する必要のないレジスタと、内容を保存する必要があるレジスタに関するルールに従います。その後、関数を呼び出して関数を呼び出し、再帰などのあらゆることを行うことができますスタックの深さが深くないため、グローバルやヒープなどに使用されるメモリに達するため、関数を呼び出して終日返すことができます。上記のmyfunの実装は、コンパイラが生成するものと非常によく似ています。

現在、ARMには多くのコアがあり、いくつかのモードとcortex-mシリーズの動作は、モードの束と異なるスタックポインターがない限り、少し異なります。また、thumbモードでthumb命令を実行する場合は、stmなどのレジスタを使用する自由を与えないプッシュおよびポップ命令を使用します。r13(sp)のみを使用し、特定のサブセットのみをすべてのレジスタに保存することはできません。人気のアームアセンブラを使用すると、

Push {r5,r6}
...
pop {r5,r6}

アームコードとサムコードで。アームコードの場合、適切なstmdbおよびldmiaをエンコードします。 (親指モードでは、いつ、どこでdbを使用するか、前にデクリメントする、そしてia、後にインクリメントするかを選択することもできません)。

いいえ、絶対に同じレジスタを使用する必要はなく、同じ数のレジスタをペアにする必要もありません。

Push {r5,r6,r7}
...
pop {r2,r3}
...
pop {r1}

これらの命令の間に他のスタックポインターの変更がないと仮定すると、プッシュで12バイト減少することを覚えている場合、プッシュは0x1000から0x0FF4に、r5は0xFF4に、r6は0xFF8に、r7は0xFFCに書き込みますポインターは0x0FF4に変わります。最初のポップは0x0FF4の値を取り、それをr2に入れ、次に0x0FF8の値を入れ、r3に入れてスタックポインターが値0x0FFCを取得します。後の最後のポップでは、spは読み取られる0x0FFCであり、値はr1に配置され、スタックポインターは値0x1000を取得し、開始されます。

ARM ARM、ARM Architectural Reference Manual(infocenter.arm.com、リファレンスマニュアル、ARMv5のマニュアルを見つけてダウンロードします。これは従来のARMです。 ARMおよびThumb命令を含むARMには、ldmおよびstmの疑似コードが含まれていますARMこれらの使用方法の全体像を示す命令。同様に、本全体は腕とそれをプログラムする方法についてです。プログラマモデルの章の前で、すべてのモードのすべてのレジスタについて説明します。

ARMプロセッサをプログラミングしている場合は、正確にどれを決定するか(チップベンダーが教えてください、ARMはチップを製造せ​​ず、チップベンダーがチップを入れるコアを作成します)あなたが持っているコア。次に、armのWebサイトにアクセスして、そのファミリのARM ARMを見つけ、ベンダーがそれを提供している場合はリビジョンを含む特定のコアのTRM(テクニカルリファレンスマニュアル)を見つけます(r2p0はリビジョン2.0(2ポイントゼロ、2p0))、より新しいrevがある場合でも、ベンダーが設計で使用したマニュアルと一致するマニュアルを使用します。すべてのコアがすべての命令またはモードをサポートしているわけではありません。TRMは、サポートされているモードと命令を通知しますARM ARMは、そのコアが存在するプロセッサフ​​ァミリ全体の機能に毛布を投げます。 ARM7TDMIはARMv7ではなくARMv4であり、ARM9はARMv9ではないことに注意してください。 ARMvNUMBERはファミリ名ARM7、vのないARM11はコア名です。新しいコアには、ARMNUMBERの代わりにCortexやmpcoreなどの名前が付けられているため、混乱が軽減されます。もちろん、非常に異なるファミリであるARMv7-m(cortex-MNUMBER)とARMv7-a(Cortex-ANUMBER)を作成して混乱を取り戻さなければなりませんでした。マイクロコントローラー、コーヒーメーカーの時計、点滅ライトなど。 google beagleboard(Cortex-A)およびstm32 value line discovery board(Cortex-M)を使用して、違いを感じてください。または、ギガヘルツ以上の複数のコアを使用するopen-rd.orgボード、またはnvidiaの新しいtegra 2、同じディールスーパースケーラー、マルチコア、マルチギガヘルツでも使用できます。 cortex-mは100MHzの障壁をかろうじて壊し、メモリはキロバイト単位で測定しますが、cortex-aがそれほど多くない場所に移動したい場合は、おそらく数か月間バッテリーを使用します。

非常に長い投稿で申し訳ありませんが、それが有用であることを願っています。

42
old_timer