これは非常に簡単な例です。
.globl _start
_start:
adr r0,_start
ldr r1,_TEXT_BASE
...
_TEXT_BASE: .word _start
組み立てて、リンクしてから分解すると、次のようになります。
00008000 <_start>:
8000: e24f0008 sub r0, pc, #8
8004: e59f101c ldr r1, [pc, #28] ; 8028 <_TEXT_BASE>
...
00008028 <_TEXT_BASE>:
8028: 00008000 andeq r8, r0, r0
そして、あなたの答えがあります。adr 命令は、実行時に PC に 0x8008 が含まれているという前提に基づいています。LDR は、どこにいても同じリンク時間値を取り込みます。
たとえば、このコードが実際にアドレス 0x20000000 にある場合、その最初の命令 (adr は疑似命令であり、逆アセンブリでは 8 のサブです)、adr が実行されると、0x20000008-8 = 0x20000000 が取得され、それを 0x8000 と比較すると、一致しません。コードを 0x8000 で実行している場合、0x8008-8 = 0x8000 となり、2 つが一致します。
コードを読んでadr命令を調べてください(または、私がしたことをして、コンパイラ/ツールの出力を調べて、答えが表示されない場合はハードウェアで実行してください)。
編集:
さまざまな接頭辞を持つ gnu ツールを使用しますが、このコードはほとんど気にしないほど単純です。arm-none-eabi- または arm-none-linux-gnueabi-。
アセンブリ ファイル名が foo.s であると仮定します。
arm-none-eabi-as foo.s -o foo.o
arm-none-eabi-ld -T memmap foo.o -o foo.elf
arm-none-eabi-objdump -D foo.elf
私が memmap と呼んでいるものはリンカー スクリプトです。この場合、コマンド ライン -Ttext=0x8000 を使用できます。また、クロス コンパイル バイナリに .elf 拡張子を使用するのが好きですが、誰もがそうしているわけではありません。
00008000 <_start>:
8000: e24f0008 sub r0, pc, #8
8004: e59f101c ldr r1, [pc, #28] ; 8028 <_TEXT_BASE>
...
00008028 <_TEXT_BASE>:
8028: 00008000 andeq r8, r0, r0
_start は、リンカがこのラベルにコードのエントリ ポイントがどこにあるかを知る必要がある/必要とする gnu ツールのものです。したがって、ubootも気にするかもしれませんが、実際にはubootのことではありませんが、間違いなくgnuリンカのことです。
0x8000 は、エントリ ポイントとしての ARM ベースの Linux プログラムの珍しいアドレスではありません。Linux は、そのようなアドレスで開始されたカーネルとして表示されますが、システムとバイナリを何にでもセットアップできるのは本当に任意です。
ここで何が起こっているかについて、実際にはARM固有または魔法は何もありません。適切な指示を考え出さなければならないプラットフォームに関係なく、同じ考えです。
アーム内のプログラム カウンターは 2 命令先です。これは 32 ビット アーム命令であるため、命令を実行するときのプログラム カウンターは、この命令のアドレス + 8 であると想定されます。この adr は _start の最初の命令であるため、これはこれを意味します命令は、リンク/コンパイルされたアドレス 0x8000 にあり、0x8008 から 0x8000 に到達する結果として、命令は r0 = pc-8 としてエンコードされます。その他の情報は、リンカがラベル _TEXT_BASE の後に _start のアドレスを提供することです。したがって、もう 1 つのステップは、その値を r1 にロードすることです。
これは、プロセッサがアドレス 0x8000 のフラッシュ内の命令を認識できるように、コードが実際にフラッシュ内に存在するという仮定と事実に基づいて操作する場合にのみ機能します。0x8000 と 0x8000 の比較は等しいので、プログラムは自分自身のコピーを RAM に作成し、このコピーがある場所の先頭にジャンプします。今回は、コードのコピーを通過するときに、1 つのレジスタにいくつかのアドレスが含まれます。 0x8000 以外なので比較は失敗します。フラッシュから元のバージョンを実行している場合、または RAM からコピーを実行している場合、これは単なる検出器です。目的は、コピーを作成し、RAM ベースのコピーを実行することです。コードが両方のアドレスで実行できることを保証するには、他の予防措置が必要です (位置に依存しないコード)。
たまたまフラッシュ アドレスが 0x00008000 であることがわかっていて、たとえば RAM アドレスが 0x20000000 であることがわかっている場合は、代わりに pc を 0x10000 と比較するか、pc の値を 0xFF000000 と比較することができます。
and r0,pc,#0xFF000000
beq stack_setup
しかし、これはより一般的なコードであると想定しているため、追加の命令と正確な比較が使用されています。
繰り返しますが、このタイプのトリックはかなり一般的であり、フラッシュまたは RAM コピーなどを検出します。使用される正確な指示と、リンカーに情報を入力させる方法は、ターゲットに固有です。
この場合、おそらくフラッシュはゼロ以外のアドレスにあり、0x8000 は RAM のコピーです。知らない。