アセンブラーを研究しているときに、これらの用語に出くわしました。私が得たアイデアは次のようなものです。再配置可能なマシンコードでは、コードはstatic RAM locationに依存していません。アセンブラーはRAMは私のプログラムに必要ですメモリは、リンカがそれらのためのスペースを見つけるところならどこにでも配置できます。
アイデアは正しいですか?もしそうなら、それはアセンブラによってどのように行われますか?
そして、絶対機械コードの例は何ですか?
多くの/ほとんどの命令セットにはpc相対アドレッシングがあります。つまり、実行中の命令のアドレスに関連するプログラムカウンターのアドレスを取得し、それにオフセットを追加して、メモリへのアクセスや分岐などに使用します。それ。それはあなたが再配置可能と呼んでいるものです。その命令がアドレス空間のどこにあっても、ジャンプしたいのは相対的なものだからです。コードとデータのブロック全体を他のアドレスに移動しても、それらは比較的同じ距離だけ離れているため、相対アドレッシングは引き続き機能します。イコールスキップの場合、3つの命令がどこにあっても次の命令が機能します(イフスキップ、スキップされている命令、スキップ後の命令)。
Absoluteは絶対アドレスを使用し、この正確なアドレスにジャンプし、この正確なアドレスから読み取ります。等しい場合は、0x1000に分岐します。
アセンブラーはこれを行わず、コンパイラーやプログラマーが行います。一般に、特にコードがリンクされた個別のオブジェクトで構成されている場合は特に、コンパイルされたコードは最終的に絶対アドレス指定になります。コンパイル時に、コンパイラはオブジェクトがどこに到達するかを知ることができず、外部参照がどこにあるか、またはどのくらい離れているかを知ることができないため、一般に、PC相対アドレッシング(通常、範囲制限があります)に十分に近いとは想定できません。 。そのため、コンパイラーは、リンカーが絶対アドレスを入力するためのプレースホルダーを生成することがよくあります。これは、操作と命令セット、およびこの外部アドレスの問題の解決方法に関するその他のいくつかの要因に依存します。結局、プロジェクトのサイズに基づいていますが、リンカーは最終的にいくつかの絶対的なアドレス指定を行います。したがって、非デフォルトは通常、位置に依存しないコードを生成するためのコマンドラインオプションです。たとえば、PICはコンパイラでサポートされているものです。コンパイラーとリンカーの両方は、それらの項目を位置に依存しないようにするために追加の作業を行う必要があります。アセンブリ言語プログラマはこれをすべて自分で行う必要があります。通常、アセンブラはこれに関与せず、生成するように指示した命令のマシンコードを作成するだけです。
novectors.s:
.globl _start
_start:
b reset
reset:
mov sp,#0xD8000000
bl notmain
ldr r0,=notmain
blx r0
hang: b hang
.globl dummy
dummy:
bx lr
こんにちはC
extern void dummy ( unsigned int );
int notmain ( void )
{
unsigned int ra;
for(ra=0;ra<1000;ra++) dummy(ra);
return(0);
}
memap(リンカースクリプト)MEMORY {ram:Origin = 0xD6000000、LENGTH = 0x4000} SECTIONS {.text:{(。text)}> ram} Makefile
ARMGNU = arm-none-eabi
COPS = -Wall -O2 -nostdlib -nostartfiles -ffreestanding
all : hello_world.bin
clean :
rm -f *.o
rm -f *.bin
rm -f *.elf
rm -f *.list
novectors.o : novectors.s
$(ARMGNU)-as novectors.s -o novectors.o
hello.o : hello.c
$(ARMGNU)-gcc $(COPS) -c hello.c -o hello.o
hello_world.bin : memmap novectors.o hello.o
$(ARMGNU)-ld novectors.o hello.o -T memmap -o hello_world.elf
$(ARMGNU)-objdump -D hello_world.elf > hello_world.list
$(ARMGNU)-objcopy hello_world.elf -O binary hello_world.bin
hello_world.list(気になる部分)
Disassembly of section .text:
d6000000 <_start>:
d6000000: eaffffff b d6000004 <reset>
d6000004 <reset>:
d6000004: e3a0d336 mov sp, #-671088640 ; 0xd8000000
d6000008: eb000004 bl d6000020 <notmain>
d600000c: e59f0008 ldr r0, [pc, #8] ; d600001c <dummy+0x4>
d6000010: e12fff30 blx r0
d6000014 <hang>:
d6000014: eafffffe b d6000014 <hang>
d6000018 <dummy>:
d6000018: e12fff1e bx lr
d600001c: d6000020 strle r0, [r0], -r0, lsr #32
d6000020 <notmain>:
d6000020: e92d4010 Push {r4, lr}
d6000024: e3a04000 mov r4, #0
d6000028: e1a00004 mov r0, r4
d600002c: e2844001 add r4, r4, #1
d6000030: ebfffff8 bl d6000018 <dummy>
d6000034: e3540ffa cmp r4, #1000 ; 0x3e8
d6000038: 1afffffa bne d6000028 <notmain+0x8>
d600003c: e3a00000 mov r0, #0
d6000040: e8bd4010 pop {r4, lr}
d6000044: e12fff1e bx lr
ここで示しているのは、位置に依存しない指示と位置に依存する指示の混合です。
たとえば、これらの2つの命令は、リンカが入力する必要がある.Wordスタイルのメモリロケーションをアセンブラに強制的に追加するショートカットです。
ldr r0,=notmain
blx r0
0xD600001cはその場所です。
d600000c: e59f0008 ldr r0, [pc, #8] ; d600001c <dummy+0x4>
d6000010: e12fff30 blx r0
...
d600001c: d6000020 strle r0, [r0], -r0, lsr #32
そして、それは絶対アドレスであるアドレス0xD6000020で埋められるので、そのコードが機能するためにはnotmainはアドレス0xD6000020になければならず、それは再配置可能ではありません。例のこの部分は、位置に依存しないコードも示しています。
ldr r0, [pc, #8]
この命令セットの動作方法について話していたPC相対アドレス指定です。実行時のPCは2命令先、または基本的にこの場合、命令がメモリ内の0xD600000cにある場合、実行時にPCは0xD6000014となり、追加されます。命令の状態として8から8になり、0xD600001Cを取得します。しかし、まったく同じマシンコード命令をアドレス0x1000に移動した場合、[〜#〜] and [〜#〜]は、読み取っているもの(0xD6000020)を含め、周囲のすべてのバイナリをそこに移動します。基本的にこれを行います:
1000: e59f0008 ldr r0, [pc, #8]
1004: e12fff30 blx r0
...
1010: d6000020
そして、それらの命令は、そのマシンコードはまだ機能し、再アセンブルまたは再リンクする必要はありません。 0xD6000020コードsitllは、ldr pcとblxがその固定アドレスビットにある必要はありません。
逆アセンブラはこれらを0xd6 ...ベースのアドレスで示していますが、blおよびbneもpc相対であり、命令セットのドキュメントを参照することで確認できます。
d6000030: ebfffff8 bl d6000018 <dummy>
d6000034: e3540ffa cmp r4, #1000 ; 0x3e8
d6000038: 1afffffa bne d6000028 <notmain+0x8>
0xD6000030を実行するとpcは0xD6000038となり、0xD6000038-0xD6000018 = 0x20、つまり8命令になります。そして、2の補数の負の8は0xFFF..FFFF8です。そのマシンコードebfffff8の大部分はffff8であることがわかります。これは、基本的に後方8分岐を示すために符号拡張され、プログラムカウンターに追加されるものです。同じことが1afffffaのffffaにも当てはまります。つまり、等しくない場合、6つの命令を逆方向に分岐します。この命令セット(アーム)は、pcが2命令先であることを想定しているため、バック6は2つ前に、次に6を返し、または実質的に4を返します。
あなたが削除した場合
d600000c: e59f0008 ldr r0, [pc, #8] ; d600001c <dummy+0x4>
d6000010: e12fff30 blx r0
次に、このプログラム全体が位置に依存しなくなり、偶然に(たまたまそれが起こることを知っていたのですが)、それを行うようにツールに指示したからではなく、すべてを接近させて絶対アドレス指定を使用しなかったからです。
最後に、「リンカーがスペースを見つける場所」と言ったときに、リンカースクリプトですべてに0xD6000000から始めるようにリンカーに指示した場合、ファイル名や関数は指定しなかったので、特に指示がない限り、このリンカーはアイテムを配置しますコマンドラインで指定された順序で。 hello.cコードは2番目なので、リンカがnovectors.sコードを配置した後、リンカの余地があった場所はどこでも、hello.cコードは0xD6000020から始まります。
また、位置に依存しないものとそうでないものをそれぞれの命令を調査することなく確認する簡単な方法は、コードを他のアドレスに配置するようにリンカースクリプトを変更することです。
MEMORY
{
ram : Origin = 0x1000, LENGTH = 0x4000
}
SECTIONS
{
.text : { *(.text*) } > ram
}
そして、もしあればどのマシンコードが変更され、何が変更されないかを確認します。
00001000 <_start>:
1000: eaffffff b 1004 <reset>
00001004 <reset>:
1004: e3a0d336 mov sp, #-671088640 ; 0xd8000000
1008: eb000004 bl 1020 <notmain>
100c: e59f0008 ldr r0, [pc, #8] ; 101c <dummy+0x4>
1010: e12fff30 blx r0
00001014 <hang>:
1014: eafffffe b 1014 <hang>
00001018 <dummy>:
1018: e12fff1e bx lr
101c: 00001020 andeq r1, r0, r0, lsr #32
00001020 <notmain>:
1020: e92d4010 Push {r4, lr}
1024: e3a04000 mov r4, #0
1028: e1a00004 mov r0, r4
102c: e2844001 add r4, r4, #1
1030: ebfffff8 bl 1018 <dummy>
1034: e3540ffa cmp r4, #1000 ; 0x3e8
1038: 1afffffa bne 1028 <notmain+0x8>
103c: e3a00000 mov r0, #0
1040: e8bd4010 pop {r4, lr}
1044: e12fff1e bx lr
受け入れられた答えが必ずしもここで正しいかどうかはわかりません。再配置可能コードと、位置独立コードと見なされるものとの間には、根本的な違いがあります。
今私は長い間、多くの異なるアーキテクチャでアセンブリをコーディングしており、マシンコードは常に3つの特定のフレーバーであると考えてきました。
最初にposition-independentコードについて説明しましょう。これは、アセンブルされたときに、すべての命令が相互に関連するコードです。したがって、たとえばブランチは、現在の命令ポインター(またはプログラムカウンターのいずれかを呼び出す)からのオフセットを指定します。位置に依存しないコードは、コードの1つのセグメントのみで構成され、そのデータもこのセグメント(またはセクション)に含まれます。同じセグメント内に埋め込まれたデータには例外がありますが、これらは通常、オペレーティングシステムまたはローダーから渡されるメリットです。
これは、オペレーティングシステムが実行を開始できるようにするために、オペレーティングシステムがポストローディング操作を実行する必要がないことを意味するため、非常に便利なタイプのコードです。メモリにロードされている場所であればどこでも実行できます。もちろん、このタイプのコードには問題もあります。つまり、親族が範囲外に移動し始める前に、異なるメモリタイプやサイズの制限に適したコードとデータを分離できないなどです。
Relocatable-Codeは、多くの点で位置に依存しないコードに非常に似ていますが、非常に微妙な違いがあります。名前が示すように、このタイプのコードはメモリ内のどこにでもロードできるという点で再配置可能ですが、通常、実行可能になる前に再配置または修正する必要があります。実際、このタイプのコードを使用する一部のアーキテクチャは、コードの再配置可能な部分を修正するというまさにその目的のために、「reloc」セクションのようなものを埋め込みます。このタイプのコードの欠点は、コードが再配置されて修正されると、本質的にほとんど絶対的なものになり、そのアドレスで修正されることです。
再配置可能コードに主な利点と、最も普及しているコードである理由は、コードをセクションに簡単に分割できることです。各セクションは、要件に合わせてメモリ内の任意の場所にロードできます。その後、再配置中に、別のセクションを参照するコードを再配置テーブルで修正できるため、セクションを適切に結合できます。通常、コード自体は相対的です(x86アーキテクチャの場合と同様)。ただし、範囲外の可能性があるものはすべて、再配置可能な命令としてアセンブルできるため、ロードアドレスに追加されたオフセットで構成されます。また、相対アドレッシングによって課せられる制限はもはや問題ではなくなります。
コードの最終的なタイプはAbsolute-Codeです。このコードは、1つの特定のアドレスで動作するようにアセンブルされ、その特定のアドレスでロードされたときにonlyのみ動作します。分岐およびジャンプ命令にはすべて、固定された正確な(絶対)アドレスが含まれています。これは通常、組み込みシステムで見られるタイプのコードで、コードがその特定のアドレスにロードされることを保証できます。最近のコンピューターでは、このような絶対コードは機能しません。なぜなら、空きメモリーがある場所にコードをロードする必要があり、特定のメモリー範囲が使用可能であるという保証は決してないからです。ただし、絶対コードにはその利点があります。主に、一般に実行が最も高速ですが、これはプラットフォームに依存する場合があります。
コード内に実際にアドレスが含まれているものには、絶対アドレスがあります。コード内にアドレスを含まないプログラム(すべてが相対アドレスで行われる)は、どのアドレスからでも実行できます。
アセンブラはこれを行わず、プログラマが行います。私は過去に少しやりましたが、小さなものの場合は通常は簡単です。相対ジャンプの範囲を超えると、かなり苦痛になります。 IIRCの唯一の2つのアプローチは、ルーチン間の相対ジャンプをスリップするか、既知のオフセットを現在のアドレスに追加し、それをプッシュしてから戻ることです。昔はそれを計算してコードに書き込むという3番目のアプローチがありましたが、それはもはや受け入れられません。他の方法がないことを私が断言しないほど十分に長い間です。
IIRCは、絶対アドレスなしで何かを「呼び出す」唯一の方法は、戻りたいアドレスをプッシュし、アドレスを計算し、それをプッシュして返すことです。
実際には、通常、ハイブリッドアプローチを使用することに注意してください。アセンブラとリンカは、調整を行うために必要な情報を格納します。プログラムがメモリにロードされると、ロードされたアドレスで実行されるように変更されます。したがって、メモリ内の実際のイメージは絶対的ですが、ディスク上のファイルは相対的なように機能しますが、通常発生するすべての問題はありません。 (同じアプローチが、実際にネイティブコードを生成するすべての高レベル言語で使用されることに注意してください。)
基本的に、「絶対」モードは、コードとRAM変数がアセンブラーに指示するとおりに配置されることを意味し、「再配置可能」は、アセンブラーがコードチャンクをビルドしてRAMを指定することを意味します_リンカがスペースを見つける場所に配置できるニーズ。
「再配置可能」とは、アセンブラがコードチャンクをビルドし、リンカがそれらの余地を見つけた場所に配置できるRAMニーズを指定することを意味します。