ARMアセンブラー(続き)

こんにちは、ハラジテリ。 ARMの記事に触発されて、興味のある人や私のような初心者向けにこの記事を続けることにしました。 タイトルに基づいて、この記事を読む前に上記を読むことをお勧めします。 それで、続けます。



私のケースは、次の点で前のケースと異なります。



原則として、「親」記事のケースに関する重大な変更はありません。

前の記事のプログラムが保存されたフラッシュメモリは、一種のEEPROM(電気消去機能付きフラッシュプログラム可能ROM)です。 これは便利な「セカンダリ」メモリであり、通常はハードディスクとして使用されますが、変数を保存するには不便です。 変数は、簡単に変更できるようにRAMに保存する必要があります。

QEMUパッケージによってエミュレートされるボードには、アドレス0xA000 0000から始まる64 MBのRAMがあり、そこに変数を保存できます。 エミュレートされたボードのメモリカードを図に示します。

画像

このアドレスから始まる変数を配置するには、特別な対策を講じる必要があります。 何を行う必要があるかを正確に理解するには、リンカー(リンカー)が果たす役割を理解する必要があります。



リンカー



複数のソーステキストファイルで構成されるプログラムのブロードキャスト中に、そのような各ファイルはオブジェクトファイルに変換されます。 リンカーは、これらのオブジェクトファイルを最終的な実行可能ファイルに結合します。

画像

リンク中に、リンカーは次の操作を実行します。

  1. 文字解像度
  2. 引越し


文字解像度



ソースファイルをオブジェクトコードに変換する際、トランスレーターはラベルへのすべての参照を対応するアドレスに置き換えます。 マルチファイルプログラムでは、モジュールに別のファイルで定義された外部ラベルへのリンクがある場合、アセンブラはそれらを「未解決」としてマークします。 これらのオブジェクトファイルがリンカーに転送されると、他のオブジェクトファイルからそのようなリンクのアドレス値が決定され、正しい値に合わせてコードが修正されます。

特別に2つのファイルに分割された配列の要素の合計を計算する例を考えてみましょう。これにより、リンカによって実行される文字の解像度が明確に表示されます。 未解決のリンクの存在を示すために、両方のファイルを収集し、それらのシンボルテーブルを確認します。

sum-sub.sファイルにはsumサブルーチンが含まれ、main.sファイルは必要な引数を使用してサブルーチンを呼び出します。 ソースファイルは以下のとおりです。



main.s

.text





b start





arr: .byte 10, 20, 25 @ ( )





eoa: @ + 1





.align





start:





ldr r0, =arr @ r0 = &arr





ldr r1, =eoa @ r1 = &eoa





bl sum @ sum





stop: b stop







合計

@





@ r0:





@ r1:







@





@ r3:







.global sum





sum: mov r3, #0 @ r3 = 0





loop: ldrb r2, [r0], #1 @ r2 = *r0++;





add r3, r2, r3 @ r3 += r2;





cmp r0, r1 @ if (r0 != r1);





bne loop @ , «goto loop» 86





mov pc, lr @ pc = lr;







.global



ディレクティブを使用して、他のファイルの関数で宣言された変数の可視性を設定します。 ファイルをコンパイルし、 nmコマンドを使用してシンボルテーブルを確認します。



$ arm-none-linux-gnueabi-nm main.o

00000004 t arr

00000007テア

00000008 t開始

00000014 t停止

U合計



$ arm-none-linux-gnueabi-nm sum-sub.o

00000004 tループ

00000000 T合計



2列目の1文字は、文字の種類を定義します。 タイプ「t」は、文字が.textセクションで定義されていることを意味します。 タイプ「u」は、文字が未定義であることを示します。 大文字は、.globalアクセスタイプのメンバーシップを示します。 明らかに、合計シンボルはsum-sub.oで定義され、main.oには記述されていません。リンカーが後でシンボリックリンクを変換し、実行可能ファイルを作成すると仮定します。



引越し



移動とは、ラベルにすでに設定されているアドレスを以前に変更するプロセスであり、新しく割り当てられたアドレスを反映するようにすべてのリンクを修正するプロセスです。 まず、移動は次の2つの理由で実行されます。

  1. セクション結合
  2. 実行可能ファイルでのセクションの配置


移動プロセスを理解するには、セクションが何であるかを理解することが重要です。

プログラムの実行時に、コードとデータはさまざまな方法で処理できます。コードをROM(読み取り専用メモリ)に配置できる場合、データの読み取りと書き込みの両方が必要になる場合があります。 コードとデータが交互になっていない場合が最も便利です。そのため、プログラムはセクションに分割されています。 ほとんどのプログラムには少なくとも2つのセクションがあります。コード用の.textとデータを操作するための.dataです。 アセンブラディレクティブ.textおよび.dataは、2つのセクションを切り替えるために使用されます。

アセンブラは、セクションディレクティブを検出すると、それに続くコードまたはデータを対応するメモリ領域に配置します。 したがって、1つのセクションに属するコードとデータは隣接するセルにあります。 このプロセスを次の図に示します。

画像



セクション結合


マルチファイルプログラムでは、同じ名前(たとえば、.text)のセクションが異なるファイルに表示される場合があります。 リンカは、入力ファイルのセクションを出力ファイルのセクションにマージします。 デフォルトでは、各ファイルから同じ名前のセクションが順番に配置され、タグへのリンクは新しいアドレスの値によって調整されます。

セクションをマージした結果は、オブジェクトファイルと対応する実行可能ファイルのシンボルテーブルを使用して確認できます。 配列合計計算プログラムの例とともに、マージ結果を以下に示します。



$ arm-none-linux-gnueabi-ld -Ttext = 0x0 -o sum.elf main.o sum-sub.o

$ arm-none-linux-gnueabi-nm sum.elf

00000004 t arr

...

00000007テア

00000024 tループ

00000008 t開始

U _start

00000014 t停止

00000020 T合計



sum-suboの.textセクションは移動し、main.oの.textセクションの直後にあるため、ループシンボルのアドレスはsum-sub.oで0x4、sum.elfで0x24です。



実行可能ファイルでのセクションの配置


プログラムがコンパイルされるとき、各セクションはアドレス0から始まり、ラベルにはセクションの先頭からの相対値が割り当てられると想定されます。 実行可能ファイルを作成すると、セクションがXアドレスに配置され、セクションで定義されたラベルへのリンクがXずつ増加します。

特定のメモリ領域への各セクションの配置と、セクション内のラベルへのすべての参照の修正は、リンカーによって実行されます。

セクションの配置の結果は、オブジェクトおよび実行可能ファイルのシンボルテーブルから確認できます。 理解を深めるために、.textセクションを0x100に配置してください。 その結果、.textセクションのアドレスは、実行可能ファイルでさらに100になります。 セクションのマージ(セクションのマージ)および配置(セクションの配置)のプロセスを図に示します。

画像



リンカースクリプトファイル



前述のように、セクションのマージと配置はリンカーによって行われます。 セクションの結合方法およびセクションのメモリ領域の配置は、リンカスクリプトファイルを使用して制御できます。 以下は、非常に単純なスクリプトの例で、その主要な場所にはデジタルタグが付いています。

SECTIONS { ❶





. = 0x00000000; ❷





.text : { ❸





abc.o (.text);





def.o (.text);





} ❹





}





❶SECTIONSは、最も重要なリンカコマンドであり、セクションの結合方法と配置場所を決定します。

SECTIONSコマンドの次のブロックには、時間の数値(ロケーションカウンター)が示されています。 デフォルトでは、場所は常に0x0に初期化されますが、別の値を指定して初期化を変更できます。 この場合、ゼロに設定することは不要なアクションです。

❸-❹スクリプトのこの部分は、ソースファイルabc.oおよびdef.oの.textセクションが出力ファイルの.textセクションに移動する必要があることを決定します。

リンカースクリプトは、ファイル名を指定する代わりにsyvolol“ *”を導入することにより、さらに単純化および一般化できます。

SECTIONS {





. = 0x00000000;





.text : { * (.text); }





}





プログラムに.textセクションと.dataセクションの両方が含まれている場合、以下に示すように、.dataセクションの結合と配置を実行できます。

SECTIONS {





. = 0x00000000;





.text : { * (.text); }







. = 0x00000400;





.data : { * (.data); }





}





ここで、.textセクションは0x0に配置され、.dataセクションは0x400に配置されます。 ロケーションカウンタに値が割り当てられていない場合、セクションは隣接するメモリ領域に配置されます。



リンカースクリプトの例





リンカースクリプトの使用方法を示すために、最新のスクリプトを使用して、プログラムセクションの.textおよび.dataの場所を制御します。 このために、プログラムのバージョンを変更して、配列の要素の合計を計算します。

.data





arr: .byte 10, 20, 25





eoa:







.text





start:





ldr r0, =eoa @ r0 = &eoa





ldr r1, =arr @ r1 = &arr





mov r3, #0 @ r3 = 0





loop:





ldrb r2, [r1], #1 @ r2 = *r1++





add r3, r2, r3 @ r3 += r2





cmp r1, r0 @ if (r1 != r2)





bne loop @ goto loop





stop: b stop







ご覧のとおり、配列は.dataセクションに配置されています。 スクリプトがセクションを正しく配置するため、データをジャンプするための指示は不要になりました。

プログラムがコンパイルされると、スクリプトは入力としてリンカーに渡されます。



$ arm-none-linux-gnueabi-as -o sum-data.o sum-data.s

$ arm-none-linux-gnueabi-ld -T sum-data.lds -o sum-data.elf sum-data.o



-T sum-data.ldsパラメーターは、sum-data.ldsファイルをリンカースクリプトとして指定します。 通常のメモリ内のセクションの配置は、シンボルテーブルによって追跡できます。



$ arm-none-linux-gnueabi-nm -n sum-data.elf



00000000 t開始

0000000c tループ

0000001c t停止

00000 400 d arr

00000403 d eoa





ご覧のとおり、.textセクションは0x0にあり、.dataセクションは0x400にあります。



これは私の最初の投稿であるため、多くをロードして巨大にしたくありません。 したがって、この段階で終了します。 おもしろくて、リクエストがあれば、この新しい記事を続けて、次のような問題に触れます。




All Articles