1. ELF
実行可能リンク形式(ELF)ファイルは、x86Linuxシステムでの一般的なオブジェクトファイル形式で、主に3つのタイプがあります。
(1)接続に適した再配置可能ファイル(再配置可能ファイル)を使用して、実行可能ファイルを作成し、オブジェクトファイルを他のオブジェクトファイルと共有できます。
(2)プログラムのプロセスイメージを提供し、ロードされたメモリで実行するために使用される、実行に適した実行可能ファイル(実行可能ファイル)。
(3)共有オブジェクトファイル(共有オブジェクトファイル)、コネクタはそれを他の再配置可能ファイルおよび共有オブジェクトファイルと他のオブジェクトファイルに接続でき、ダイナミックリンカーは実行可能ファイルと他の共有オブジェクトファイルと接続できます組み合わせてプロセスイメージを作成します。
ELFファイル形式はより複雑です。
第二に、HEX
インテルHEXファイルは、テキスト行を記録するASCIIテキストファイルです。インテルHEXファイルでは、各行はHEXレコード、16進数で構成されるマシンコードまたはデータ定数です。インテルHEXファイルは、プログラムまたはデータによく使用されます。
ストレージをROM、EPROMに転送し ます。ほとんどのプログラマーとエミュレーターはIntel HEXファイルを使用します。
レコード形式
インテルHEXファイルには、任意の数の16進レコードを含めることができます。各レコードには5つのフィールドがあり、以下はレコード形式です。
:llaaaatt [dd。。。ccの
各文字グループは独立したフィールドです。各文字は16進数字です。各フィールドは少なくとも2つの16進数字で構成されます。次に、バイトの説明を示します。
:コロンは各Intel HEXレコードの先頭です
。Llはこのレコードの長さフィールドです。コロンは データのバイト数(dd)を表します。
aaaaはアドレスフィールドであり、彼はデータの開始アドレスを示します
ttこのフィールドは、このHEXレコードのタイプを示します。彼は次のタイプである可能性があります
00-データレコード
01-ファイルレコードの
終わり02-拡張セグメントアドレスレコード
04 —拡張線形アドレスレコード
ddはデータフィールドであり、データの1バイトを示します。レコードには複数のデータバイトが含まれる場合があり、バイト数は
llフィールドで確認できます 。cc
はチェックサムフィールドであり、レコードのチェックサムを示します。計算方法は、このレコードのコロンで始まるすべての文字ペアを照合することです
表現
された16進数字 が加算され、256の剰余除算によって得られた剰余は、最終的にはチェックバイトccである剰余の補数になります。
:0300000002005E9D
cc = 0×01 + NOT((0×03 + 0×00 + 0×00 + 0×00 + 0×02 + 0×00 + 0×5E)%0×100)= 0×01 + 0 ×9C = 0×9D>
データレコード
インテルHEXファイルは複数のデータレコードで構成されます。データレコードはキャリッジリターンとラインフィードで終わります。たとえば
、次のデータレコード
:10246200464C5549442050524F46494C4500464C33
10はこの行に記録されたデータ のバイト数です 。2462
はメモリ
00のデータの開始アドレス は、レコードタイプ00(データレコード)です
。464Cから464Cは、データ
33は、このレコード行のチェックサムです。
三、BIN
BINファイルはダイレクトバイナリファイルであり、内部にアドレスマークはありません。通常、プログラマーでプログラミングする場合は00から始まり、ダウンロードを実行するとコンパイル時のアドレスにダウンロードできます。
概要:ELFファイルから他の2つのファイルに変換できます。HEXを直接BINファイルに変換することもできますが、BINをHEXファイルに変換するには、ベースアドレスを指定する必要があります。ELFの方が情報量が多いため、HEXとBINはelfファイルに変換できません。
次のコマンドfromelf -nodebug xxを使用してBINファイルに変換できる広告デバッグファイルaxfもあります。axf -bin xx。ただビン。
ここで説明するARMシステムの基本的なファイル形式は、ARMベースの組み込みシステムの開発でよく見られるすべてのファイル形式です。
ARMシステムには3つの基本的なファイル形式があります
。1)フラットバイナリ形式のBINは、通常、フラッシュに直接書き込むために使用され、モニタープログラムにロードするためにも使用できます。
2)ELF、EXECUTABLE AND LINKABLE FORMAT、一般的なOBJECTファイル形式。一般にGNU COMPILER COLLECTION(GCC)によって作成されます。
3)BINフォーマットの拡張版であるAXFは本体がBINと同じで、AXD用のファイルの先頭と末尾にデバッグ情報が追加されます。
この記事では、主にBINとELFについて説明します。
まず、ELF形式はOBJECTファイル形式です。一般に、OBJECTファイルは、再配置可能なOBJECTファイル、実行可能OBJECTファイル、および共有OBJECTファイルの3つのカテゴリに分類できます。ELF形式のファイルもこれら3つのタイプに分類できます。
まず、再配置可能なOBJECTファイルについて説明します。このOBJECTファイルは通常、GCCのASSEMBLER(as)によって生成されます(GCCが単なるコンパイラであるとは考えないでください)。バイナリマシンコードに加えて、再配置に使用できる情報がいくつかあります。主にLINKER(ld)の入力として使用されます。LINKERはこの情報に従い、再配置が必要なシンボルを再配置して、実行可能なOBJECTファイルを生成します。ELF形式の再配置可能なOBJECTファイルは、ヘッダーとセクションで構成されています。
ヘッダーにはELFヘッダーとセクションヘッダーが含まれます。ELFヘッダーはファイルの先頭にあり、ターゲットマシンのアーキテクチャ、サイズの最終構成、ELFヘッダーサイズ、オブジェクトファイルタイプ、ファイル内のセクションヘッダーオフセット、セクションヘッダーサイズ、セクションヘッダーの項目数などの情報。セクションヘッダーは、ファイル内の各セクションのタイプ、位置、サイズ、およびその他の情報を定義します。リンカーは、ELFヘッダーを検索してセクションヘッダーのエントリを見つけ、セクションヘッダーで対応するセクションエントリを見つけて、ターゲットセクションを見つけます。
セクションに含まれる
.text :经过编译的机器代码。
.rodata :只读的数据,例如printf(“hello!”)中的字符串hello。
.data :已初始化的全局变量,局部变量将在运行时被存放在堆栈中,不会在.data或 .bss段中出现。
.bss :未初始化的全局变量,在这里只是一个占位符,在object文件中并没有实际的存储空间。
.symtab :符号表,用于存放程序中被定义的或被引用到的全局变量和函数的信息。
.rel.text :一个保存着一系列在.text中的位置的列表。这些位置将在linker把这个文件与其它object文件合并时被修改,一般来说,这些位置都是保存着一些引用到全局变量或者外部函数的指令。引用局部变量或者本地函数的指令是不需要被修改的,因为局部变量和本地函数的地址一般都是使用PC相对偏移地址的。需要注意的是,这个section 和下面的.rel.data在运行时并不需要,生成可执行的ELF object文件时会去掉这个section。
.rel.data :保存全局变量的重定位信息。一般来说,如果一个全局变量它的初始化值是另一个全局变量的地址,或者是外部函数的地址,那么它就需要被重定位。
.debug :保存debug信息。
.strtab : 一个字符串表,保存着.symtab和.debug ,和各个section的名字。.symtab,.debug 和section table里面,凡是保存name的域,其实都是保存了一个偏移值,通过这个偏移值在这个字符串表里面可以找到相应得字符串。
.symtabについて注意深く説明しましょう。
すべての再配置可能なオブジェクトファイルには.symtabがあります。このシンボルテーブルには、このオブジェクトファイルで定義および参照されているすべてのシンボルが格納されます。ソースプログラムがC言語プログラムの場合、.symtab内のシンボルはCコンパイラ(cc1)から直接取得されます。ここで言及するシンボルには主に3種類あります
。1)このオブジェクトファイルで定義されているシンボルは、他のオブジェクトファイルのグローバルシンボルにすることができます。C言語のソースプログラムでは、これは主に非静的(静的変更なし)グローバル変数と非静的関数です。ARMアセンブリ言語では、これらはEXPORT命令によってエクスポートされる変数です。
2)このオブジェクトファイルで参照されているが、他のファイルで定義されているグローバル変数。ARMアセンブリ言語では、これはIMPORTコマンドで導入された変数です
3)ローカル変数。ローカル変数は、このオブジェクトファイルでのみ表示されます。ここでのローカル変数は、コネクターのローカル変数を指します。これは、一般的なプログラムのローカル変数と区別する必要があります。ここで参照されるローカル変数には、静的で装飾されたグローバル変数、オブジェクトファイル内のセクション名、およびソースコードファイル名が含まれます。一般的な意味でのローカル変数は、実行時にシステムのランタイム環境によって管理され、リンカーは気にしません。
上記の条件を満たす各シンボルは、.symtabファイルにデータ項目を持ちます。このデータ項目のデータ構造は次のとおりです。
Typedef struct{
int name;//符号名称,其实就是.strtab的偏移值
int value;//在section中的位置,以相对section地址的偏移表示
int size;//大小
char type;//类型,一般是数据或函数
char binding;//是本地变量还是全局变量
char reserved;//保留的位
char section;//符号所属的section。可选有:.text(用数字1代表),.data(用数
//3代表),ABS(不应被重定位的符号),UND(在本object文件
//中未定义的符号,可能在别的文件中定义),COM(一般的未初//始化的变量符号)。
}ELF_sym
次に、アプリケーションを構成するさまざまなモジュールがアセンブルされ、再配置可能なオブジェクトファイルが構築されたとします。これらのオブジェクトの構造は同じで、独自の.text、.dataセクション、および独自の.symtabがあります。GCCの次のステップは、リンカー(ld)を使用してこれらのオブジェクトファイルと必要なライブラリを接続し、絶対実行時アドレスを持つ実行可能ファイルは、ELF形式の実行可能ファイルです。
リンカーの接続アクションは、次の2つの部分に分けることができます。
1)シンボル解決。参照記号の方向を決定します。
2)シンボルの再配置。セクションを結合し、ランタイム環境アドレスを割り当て、シンボルの再配置を参照します。
シンボルの解決:
オブジェクトファイルには、シンボルを定義する命令があり、一部の命令はシンボルを参照しています。参照されるシンボルに複数の定義がある場合があります。シンボル解決の役割は、このオブジェクトファイル内のシンボル参照によって実際に参照されるシンボルを決定することです。
コンパイル時に、このファイルで定義されているグローバル変数に加えて、コンパイラはシンボルテーブルエントリを生成します。このファイルで定義されていない参照シンボルが見つかった場合、コンパイラは自動的にシンボルテーブルエントリは、これらの参照を決定する作業をリンカーに任せます。アセンブラは、アセンブリ中にこれらのシンボルテーブルエントリを読み取り、.symtabを生成します。読み出し時、場合参照項目シンボルに見出さ決定することができない、アセンブラに格納された再配置データ項目と呼ばれるこれらの追加のシンボルのデータ入力、生成するrel.text又はrel.data部を、クロスリンカーによって決定されます。次に、再配置エントリのデータ構造を示します。
Typedef struct{
int offset;//指明需要被重定位的引用在object中的偏移,实际上就是需要被重定位的引用
//在object中的实际位置
int symbol;//这个被重定位的引用真实指向的符号
int type;//重定位类型:R_ARM_PC24:使用24bit的PC相对地址重定位引用
//R_ARM_ABS32:使用32bit绝对地址重定位引用
}Elf32_Rel
リンカーは、再配置データ項目によって生成される参照を解決する必要があります。リンカは、C言語で定義された規則に従って、再配置された各データ項目の各入力オブジェクトファイルで適切なシンボルを検索し、このシンボルをシンボル項目に入力します。しかし、このシンボルの実際のアドレスがわからないので、今でも実際の参照ポイントはわかりますが、この参照が指すアドレスを特定することはできません。
シンボルの再配置:
シンボルの再配置は、上記の問題を解決するために使用されます。リンカーは最初にセクションをマージします。リンカーによるオブジェクトファイルのマージプロセスは非常に単純です。通常、これは同じ属性を持つセクションのマージです。たとえば、異なるオブジェクトファイルの.textセクションは1つの.textにマージされます。同様に、.symtabセクションも.symtabにマージされました。ここには2つの問題があります
。1)オブジェクトファイルがマージされる順序。この質問は、最終的な命令とシンボルの実行アドレスに関連しています。最も重要なことは、どのセクションが一番上にあるかです。これは、ARM RAWシステムの開発で最も重要です。ARMシステムのCPUに電源が投入されると、システムは自動的にアドレス0x00000000から命令をフェッチして実行し、メモリはこのアドレスにマップされます。このアクションはプログラムできません。したがって、最初のセクションにはプログラムのエントリポイントを含める必要があります。そうしないと、システムは正常に実行できません。
2)入力部と出力端子の対応。理論的には、どのセクションも自由に出力セクションにマッピングできます。.dataセクションを.textセクションと組み合わせて、.textを出力できます。もちろん、そのような行動は無意味です。これらのセクションを入力として使用して出力セクションを生成するようにリンカーに指示する必要があります。
上記の2つの問題は、接続スクリプトと呼ばれるファイルによって制御されますの。リンカーは接続スクリプトを読み取って、入力から出力へのセクションのマッピングを決定し、プログラムのエントリポイントを設定し、実行可能ファイル全体の先頭にあるセクションを設定します。
接続スクリプトには、各セクションのアドレスを指定するという別の機能もあります。セクションのマージが完了すると、リンカは.symtabに従って、シンボルを均一にアドレス指定し、絶対ランタイムアドレスを割り当てます。このアドレスはセクションアドレスに基づいています。.textセクションのアドレスが0x00000000であると仮定すると、.text内のシンボルは、アドレス0x00000000を参照アドレスとして使用します。セクションアドレスの指定も、接続スクリプトによって行われます。組み込み開発で一般的に使用されます。text_base、data_base、およびプロジェクトのコンパイル時に指定する必要があるその他のパラメーターは、セクションのアドレス割り当てを完了するために最後に接続スクリプトに追加されます。
上記の2つのステップが完了すると、リンカは参照シンボルの再配置操作を実行します。リンカーは.relセクション(.relテキストと.relデータを含む)をトラバースし、その中の各データ項目について、シンボルフィールドに従って.symtab内の対応する参照される実アドレスを検索します(上記のアドレス割り当ての後、現在は.symtabにあります)。のシンボルには絶対実行アドレスがあります)。次に、オフセットフィールドによって提供されるオフセットに従って、このアドレスを対応する位置に入力します。
これまでのところ、シンボルの再配置作業は完了しています。リンカーは、再配置情報の保存に使用されたrel.textセクションとrel.dataセクションを削除し、セグメントヘッダーと.initセクションを追加します。ELF形式で実行可能なオブジェクトファイルを生成します。
セグメントヘッダーは、オペレーティングシステムのメモリマッピングに使用される情報を保持します。.initセクションには、_init関数が含まれています。プログラムが読み込まれると、オペレーティングシステムのプログラムローダーがセグメントヘッダーを読み取ってプログラムをユーザーのメモリ空間に読み込み、セグメントヘッダーのマッピング情報に従って、.textセグメントと.dataセグメントをそれぞれ適切なアドレスにマップします。次に、.initで_init関数を呼び出して、初期化を完了します。
ELFファイルには汎用性の利点があるため、現在一般的な開発モードでは、まずコンパイルツールを介してELFファイル形式の実行可能ファイルを生成し、外部ツールを使用してELFファイルの対応する部分を抽出してBINファイルを生成します。たとえば、有名なGNUブートローダーU-Bootはこのアプローチを採用しており、コンパイラツールセットはGCCで、BIN生成ツールはelf2binです。ARMの有名な開発環境ADSは、独自のarmccおよびarmcppコンパイラを使用していますが、GNU GCCと同じように機能します。