1)コンパイルされたバイナリはprom / flash yesに書き込まれます。USB、シリアル、i2c、jtagなどは、ブートプロセスを理解するために関係なく、デバイスによってサポートされるものに関してデバイスに依存します。
2)これは通常、マイクロコントローラーには当てはまりません。主な使用例は、ROM /フラッシュに命令を、RAMにデータを入れることです。アーキテクチャに関係なく。非マイクロコントローラー、PC、ラップトップ、サーバーの場合、プログラムは不揮発性(ディスク)からRAMにコピーされ、そこから実行されます。一部のマイクロコントローラーでは、ラムを使用できます。定義に違反しているように見えても、ハーバードを主張するものも同様です。ラムを命令側にマップすることを妨げるハーバードについては何もありません。電源がオンになった後にそこに命令を取得するメカニズムが必要です(これは定義に違反しますが、ハーバードシステムは他の役に立つためにそれを行う必要があります)マイクロコントローラとして)。
3)並べ替え。
各CPUは、設計どおりの決定論的な方法で「起動」します。最も一般的な方法は、電源投入後に実行する最初の命令のアドレスがリセットベクターにあるベクターテーブルです。ハードウェアが読み取ったアドレスは、そのアドレスを使用して実行を開始します。もう1つの一般的な方法は、よく知られたアドレスでベクターテーブルなしでプロセッサの実行を開始することです。チップには「ストラップ」、リセットを解除する前にハイまたはローに接続できるピンがあり、ロジックがさまざまな方法でブートすることがあります。CPU自体、つまりプロセッサコアをシステムの他の部分から分離する必要があります。cpuの動作方法を理解し、cpuアドレス空間の一部がフラッシュと通信できるように、チップ/システム設計者がcpuの外側にアドレスデコーダーをセットアップしていることを理解します。また、RAMを使用するものと周辺機器(uart、i2c、spi、gpioなど)を使用するものもあります。必要に応じて、同じCPUコアを使用して、異なる方法でラップできます。これは、腕やミップをベースにしたものを購入したときに得られるものです。armとmipsはCPUコアを作成します。CPUコアはチップを購入してラップしますが、さまざまな理由で、それらはブランド間で互換性がありません。これが、コア以外の何かに関して一般的なアームの質問をすることがめったにできない理由です。
マイクロコントローラーはチップ上のシステムになろうとするため、その不揮発性メモリー(フラッシュ/ ROM)、揮発性(SRAM)、およびCPUはすべて、周辺機器が混在する同じチップ上にあります。しかし、チップは、そのCPUのブート特性に一致するCPUのアドレス空間にフラッシュがマッピングされるように内部的に設計されています。たとえば、CPUのアドレス0xFFFCにリセットベクトルがある場合、1)を介してプログラムできるアドレスに応答するフラッシュ/ ROMが必要です。また、有用なプログラム用のアドレススペースに十分なフラッシュ/ ROMが必要です。チップ設計者は、これらの要件を満たすために、0xF000から始まる0x1000バイトのフラッシュを選択する場合があります。そしておそらく、彼らはより低いアドレスまたは多分0x0000にいくらかのRAMを置き、周辺機器は中央のどこかに置きます。
CPUの別のアーキテクチャは、アドレス0で実行を開始する可能性があるため、反対のことを行う必要があり、フラッシュを配置して、ゼロ付近のアドレス範囲に応答するようにします。たとえば、0x0000〜0x0FFFのようになります。そして、別の場所にラムを置きます。
チップ設計者は、CPUがどのようにブートするかを知っており、そこに不揮発性ストレージ(フラッシュ/ ROM)を配置しています。そのCPUのよく知られた動作に合わせてブートコードを記述するのは、ソフトウェアの担当者次第です。リセットベクタにリセットベクタアドレスを配置し、リセットベクタで定義したアドレスにブートコードを配置する必要があります。ツールチェーンはここで非常に役立ちます。時々、ポイントアンドクリックideまたは他のサンドボックスを使用して、ほとんどの作業を行うことができます。あなたがすることは、高レベル言語(C)でAPIを呼び出すことだけです。
ただし、フラッシュ/ ROMにロードされるプログラムは、CPUのハードワイヤードブート動作と一致する必要があります。プログラムmain()のC部分の前、およびエントリポイントとしてmainを使用する場合は、いくつかのことを行う必要があります。ACプログラマーは、初期値を持つ変数を宣言するときに、実際に機能すると期待していると想定しています。まあ、const以外の変数はRAMにありますが、初期値を持つ変数がある場合、その初期値は不揮発性RAMになければなりません。したがって、これは.dataセグメントであり、Cブートストラップは.dataをフラッシュからRAMにコピーする必要があります(通常、ツールチェーンによって決定されます)。初期値なしで宣言するグローバル変数は、プログラムが開始する前にゼロであると想定されますが、実際には想定しないでください。ありがたいことに、一部のコンパイラは初期化されていない変数について警告し始めています。これは.bssセグメントであり、CブートストラップのゼロはRAMにあり、その内容のゼロは不揮発性メモリに保存する必要はありませんが、開始アドレスとその量は保存します。繰り返しますが、ツールチェーンはここで大いに役立ちます。そして最後に最低限必要なことは、Cプログラムがローカル変数を持ち、他の関数を呼び出すことができることを期待するため、スタックポインターをセットアップする必要があるということです。その後、他のチップ固有の処理が行われるか、Cでチップ固有の処理が残ります。不揮発性メモリに保存する必要はありませんが、開始アドレスと量は保存します。繰り返しますが、ツールチェーンはここで大いに役立ちます。そして最後に最低限必要なことは、Cプログラムがローカル変数を持ち、他の関数を呼び出すことができることを期待するため、スタックポインターをセットアップする必要があるということです。その後、他のチップ固有の処理が行われるか、Cでチップ固有の処理が残ります。不揮発性メモリに保存する必要はありませんが、開始アドレスと量は保存します。繰り返しますが、ツールチェーンはここで大いに役立ちます。そして最後に最低限必要なことは、Cプログラムがローカル変数を持ち、他の関数を呼び出すことができることを期待するため、スタックポインターをセットアップする必要があるということです。その後、他のチップ固有の処理が行われるか、Cでチップ固有の処理が残ります。
armのcortex-mシリーズコアがこれを行います。スタックポインターはベクターテーブルにあり、リセット後に実行されるコードを指すリセットベクターがあります。 (とにかく通常asmを使用する)ベクターテーブルを生成するには、asmなしで純粋なCを使用します。今では、.dataをコピーしたり、.bssをゼロにしたりしないため、cortex-mベースの何かにasmを使用しない場合は、自分でそれを行う必要があります。大きな機能はリセットベクトルではなく、ハードウェアが推奨されるCの呼び出し規約に従ってレジスタを保持する割り込みベクトルであり、そのベクトルの正しいリターンを使用するため、各ハンドラーの周りに正しいasmをラップする必要はありません(または、ターゲットにツールチェーン固有のディレクティブを使用して、ツールチェーンでラップするようにします)。
たとえば、マイクロコントローラはバッテリーベースのシステムでよく使用されるため、一部の周辺機器の電源がオフになっていると一部のリセットが解除されるため、これらのサブシステムをオンにする必要があります。 。Uarts、gpiosなど。しばしば、水晶または内部発振器から直接、低めのクロック速度が使用されます。また、システム設計により、より高速のクロックが必要であることが示される場合があるため、それを初期化します。クロックがフラッシュまたはRAMに対して速すぎるため、クロックをアップする前に待機状態を変更する必要がある場合があります。uart、USB、またはその他のインターフェイスをセットアップする必要がある場合があります。その後、アプリケーションはそのことを実行できます。
コンピューターのデスクトップ、ラップトップ、サーバー、およびマイクロコントローラーは、起動/動作方法に違いはありません。それらがほとんど1つのチップ上にないことを除いて。BIOSプログラムは、CPUとは別のチップフラッシュ/ ROMにあることがよくあります。最近、x86 cpusはサポートチップであったものを同じパッケージ(pcieコントローラーなど)に引き寄せていますが、RAMとROMのほとんどはチップから離れていませんが、それでもシステムであり、正確に動作します高レベルでも同じです。CPUブートプロセスはよく知られています。ボード設計者は、CPUがブートするアドレス空間にフラッシュ/ ROMを配置します。そのプログラム(x86 pcのBIOSの一部)は上記のすべてを実行し、さまざまな周辺機器を起動し、dramを初期化し、pcieバスを列挙します。多くの場合、ユーザーがBIOS設定またはcmos設定と呼んでいたものに基づいて非常に設定可能です。これは当時の技術が使用されていたためです。問題ではありませんが、BIOSブートコードに何を変更するかを指示するために移動して変更できるユーザー設定があります。
異なる人々は異なる用語を使用します。チップブート、つまり実行される最初のコード。ブートストラップとも呼ばれます。ローダーという単語を含むブートローダーは、多くの場合、干渉することを何もしないと、一般的なブートからアプリケーションまたはオペレーティングシステムへの一般的なブートから起動するブートストラップであることを意味します。ただし、ローダー部分は、ブートプロセスを中断して、他のテストプログラムをロードできることを意味します。たとえば、組み込みLinuxシステムでubootを使用したことがある場合は、キーを押して通常のブートを停止し、テストカーネルをramにダウンロードして、フラッシュ上のカーネルの代わりにブートするか、またはまたは、新しいカーネルをダウンロードしてからブートローダーにフラッシュに書き込み、次回ブートするときに新しいものが実行されるようにすることができます。
CPU自体に関しては、周辺機器からのフラッシュからのRAMを知らないコアプロセッサです。ブートローダー、オペレーティングシステム、アプリケーションの概念はありません。CPUに送られて実行される命令のシーケンスです。これらは、異なるプログラミングタスクを互いに区別するためのソフトウェア用語です。互いにソフトウェアのコンセプト。
一部のマイクロコントローラには、チップベンダーが提供する個別のブートローダーがあり、別のフラッシュまたはフラッシュの別の領域にありますが、これらは変更できない場合があります。この場合、多くの場合、リセットが解除される前にピンをハイまたはローに接続すると、ロジックまたはブートローダーに何をすべきかを伝えているピンまたはピンのセット(私はストラップと呼びます)がありますチップにそのブートローダーを実行するよう指示し、データがフラッシュにプログラムされるのをuartで待ちます。ストラップを他の方法で設定すると、チップベンダーのブートローダーではなくプログラムが起動し、チップのフィールドプログラミングやプログラムのクラッシュからの回復が可能になります。フラッシュをプログラムできるのは、純粋なロジックだけである場合があります。これは最近よくあることですが、
ほとんどのマイクロコントローラがRAMよりもはるかに多くのフラッシュを持っている理由は、主な使用例がフラッシュから直接プログラムを実行し、スタックと変数をカバーするのに十分なRAMしか持っていないからです。場合によっては、RAMからプログラムを実行して、適切にコンパイルしてフラッシュに保存し、呼び出しの前にコピーする必要があります。
編集
フラッシュ
.cpu cortex-m0
.thumb
.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
.word hang
.word hang
.thumb_func
reset:
bl notmain
b hang
.thumb_func
hang: b .
notmain.c
int notmain ( void )
{
unsigned int x=1;
unsigned int y;
y = x + 1;
return(0);
}
flash.ld
MEMORY
{
bob : ORIGIN = 0x00000000, LENGTH = 0x1000
ted : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > bob
.rodata : { *(.rodata*) } > bob
.bss : { *(.bss*) } > ted
.data : { *(.bss*) } > ted AT > bob
}
したがって、これはcortex-m0の例であり、cortex-msはすべて、この例の範囲内で同じように機能します。この例の特定のチップには、アームアドレス空間のアドレス0x00000000にアプリケーションフラッシュがあり、RAMは0x20000000にあります。
cortex-mの起動方法は、アドレス0x0000の32ビットワードで、スタックポインターを初期化するアドレスです。この例では多くのスタックは必要ないので、0x20001000で十分です。明らかに、そのアドレスの下にRAMが必要です(アームがプッシュする方法です。最初に減算してからプッシュします。 0x2000FFFCを使用する必要はありません)。アドレス0x0004の32ビットワードは、リセットハンドラのアドレスであり、基本的にはリセット後に実行される最初のコードです。次に、その皮質mコアとチップに固有の割り込みハンドラとイベントハンドラがさらにありますが、おそらく128または256です。それらを使用しない場合、テーブルをセットアップする必要はありません、私はデモのためにいくつかを投げました目的。
この例では、コードを見るとこれらのセグメントには何も存在しないことがわかっているため、.dataや.bssを扱う必要はありません。もしあれば、私はそれに対処し、すぐにします。
したがって、スタックはセットアップ、チェック、.dataの処理、check、.bss、checkであるため、Cブートストラップ処理が行われ、Cのエントリ関数に分岐できます。一部のコンパイラは、関数を参照すると余分なジャンクを追加するためmain()そしてmainに向かう途中で、私はその正確な名前を使用しません。ここでは、Cエントリポイントとしてnotmain()を使用しました。そのため、リセットハンドラはnotmain()を呼び出し、notmain()が返された場合は、ハングすることになります。
私はツールをマスターすることを固く信じていますが、多くの人はそうではありませんが、あなたが見つけるのは、あなたがアプリやウェブページを作るのと同じくらい遠く、完全に近い自由のために、各ベアメタル開発者が自分のことをするということです。彼らは再び自分のことをします。独自のブートストラップコードとリンカスクリプトが必要です。他の人はツールチェーンに依存するか、ほとんどの作業が他の誰かによって行われるベンダーのサンドボックスでプレイします(何かが壊れると、怪我の世界にあり、ベアメタルのものが頻繁に劇的に壊れます)。
だから私が得るGNUツールでアセンブル、コンパイル、リンクする:
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: 2000 movs r0, #0
1e: 4770 bx lr
それで、ブートローダーはどのようにして物がどこにあるのかを知るのです。コンパイラが仕事をしたからです。最初のケースでは、アセンブラーがflash.sのコードを生成し、そうすることでラベルの場所がわかります(ラベルは関数名や変数名などの単なるアドレスです)。したがって、バイトをカウントしてベクターに入力する必要はありませんでした。テーブルを手動で、ラベル名を使用し、アセンブラーがそれを行いました。リセットがアドレス0x14である場合、アセンブラーがベクターテーブルに0x15を挿入した理由を尋ねます。さて、これはcortex-mであり、起動してサムモードでのみ実行されます。Thumbモードに分岐する場合、ARMモードでリセットする場合、アドレスに分岐するときにlsbitを設定する必要があります。そのため、常にそのビットセットが必要です。ラベルがベクターテーブル内でそのまま使用される場合、またはブランチへの分岐などに使用される場合、ツールのことを知っています。ツールチェーンはlsbitを設定することを知っています。したがって、ここでは0x14 | 1 = 0x15です。ハングについても同様です。これで、逆アセンブラはnotmain()の呼び出しに対して0x1Dを表示しませんが、ツールが命令を正しく構築したことを心配しないでください。
コードがnotmainにあるため、これらのローカル変数は使用されず、デッドコードです。コンパイラーは、yが設定されているが使用されていないということで、その事実についてもコメントします。
アドレス空間に注意してください。これらはすべてアドレス0x0000から始まり、そこからベクトルテーブルが適切に配置されます。.textまたはプログラム空間も適切に配置されます。notmain.cのコードの前にflash.sを取得した方法はツールを知っていると、よくある間違いは、それを正しく行わずにクラッシュして激しく焼くことです。IMOでは、最初に起動する直前に物を配置するために分解する必要があります。適切な場所に物を置いたら、毎回確認する必要はありません。新しいプロジェクトのために、またはそれらがハングする場合。
一部の人々を驚かせるのは、2つのコンパイラが同じ入力から同じ出力を生成することを期待する理由がないということです。または、設定が異なる同じコンパイラーです。clangを使用して、llvmコンパイラー最適化ありとなしでこれら2つの出力を取得します
llvm / clang最適化
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: 2000 movs r0, #0
1e: 4770 bx lr
最適化されていない
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: b082 sub sp, #8
1e: 2001 movs r0, #1
20: 9001 str r0, [sp, #4]
22: 2002 movs r0, #2
24: 9000 str r0, [sp, #0]
26: 2000 movs r0, #0
28: b002 add sp, #8
2a: 4770 bx lr
それはコンパイラが追加を最適化した嘘ですが、これらは変数にスタック上に2つのアイテムを割り当てました。これらはローカル変数であり、固定アドレスではなくスタック上にあるため、グローバルでそれを見るでしょう変更。しかし、コンパイラはコンパイル時にyを計算できることを認識し、実行時に計算する理由がないため、xに割り当てられたスタックスペースに1を、yに割り当てられたスタックスペースに2を単に配置しました。コンパイラは、内部テーブルを使用してこのスペースを「割り当て」ます。変数yにスタックプラス0、変数xにスタックプラス4を宣言します。コンパイラーは、実装するコードがC標準またはCプログラマーの期待に準拠している限り、何でもできます。コンパイラーが関数の実行中にxをスタック+ 4のままにしなければならない理由はありません。
アセンブラでダミー関数を追加した場合
.thumb_func
.globl dummy
dummy:
bx lr
そしてそれを呼び出す
void dummy ( unsigned int );
int notmain ( void )
{
unsigned int x=1;
unsigned int y;
y = x + 1;
dummy(y);
return(0);
}
出力が変わる
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f804 bl 20 <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <dummy>:
1c: 4770 bx lr
...
00000020 <notmain>:
20: b510 push {r4, lr}
22: 2002 movs r0, #2
24: f7ff fffa bl 1c <dummy>
28: 2000 movs r0, #0
2a: bc10 pop {r4}
2c: bc02 pop {r1}
2e: 4708 bx r1
ネストされた関数があるので、notmain関数は戻りアドレスを保持する必要があるため、ネストされた呼び出しの戻りアドレスを上書きできます。これは、x86などのスタックを適切に使用した場合、アームがリターンにレジスタを使用するためです。さて、なぜr4をプッシュしたのでしょうか?さて、呼び出し規約は、スタックを32ビット、1ワード境界ではなく64ビット(2ワード)境界に揃えておくように変更されました。そのため、スタックを整列させるために何かをプッシュする必要があるため、コンパイラは何らかの理由でr4を任意に選択しました。理由は関係ありません。このターゲットの呼び出し規約に従って、r4にポップするのはバグになりますが、関数呼び出しではr4を上書きせず、r0からr3を上書きできます。r0は戻り値です。多分テールの最適化を行っているように見えますが、
しかし、xとyの数学は、ダミー関数に渡されるハードコードされた値2に最適化されていることがわかります(ダミーは個別のファイル、この場合はasmに具体的にコーディングされているため、コンパイラーは関数呼び出しを完全に最適化しないため、 notmain.cのCで単純に返されるダミー関数がある場合、オプティマイザはx、y、およびダミー関数呼び出しを削除します。これらはすべてデッド/役に立たないコードであるためです)。
また、flash.sコードが大きくなったため、notmainはelsehwereであり、ツールチェーンがすべてのアドレスにパッチを適用してくれたため、手動で行う必要はありません。
参照用に最適化されていないclang
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: b082 sub sp, #8
26: 2001 movs r0, #1
28: 9001 str r0, [sp, #4]
2a: 2002 movs r0, #2
2c: 9000 str r0, [sp, #0]
2e: f7ff fff5 bl 1c <dummy>
32: 2000 movs r0, #0
34: b002 add sp, #8
36: bd80 pop {r7, pc}
最適化されたclang
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: 2002 movs r0, #2
26: f7ff fff9 bl 1c <dummy>
2a: 2000 movs r0, #0
2c: bd80 pop {r7, pc}
そのコンパイラの作成者は、スタックを揃えるためにダミー変数としてr7を使用することを選択しました。また、スタックフレームに何もない場合でも、r7を使用してフレームポインタを作成しています。基本的に、命令は最適化されている可能性があります。しかし、ポップを使用して3つの命令を返さなかったので、おそらく正しいコマンドラインオプション(プロセッサを指定)でgccを実行できると思いました。
これは主に残りの質問に答えるはずです
void dummy ( unsigned int );
unsigned int x=1;
unsigned int y;
int notmain ( void )
{
y = x + 1;
dummy(y);
return(0);
}
私は今グローバルを持っています。そのため、最適化されていない場合は.dataまたは.bssのいずれかになります。
最終出力を見る前に、itermediateオブジェクトを見てみましょう
00000000 <notmain>:
0: b510 push {r4, lr}
2: 4b05 ldr r3, [pc, #20] ; (18 <notmain+0x18>)
4: 6818 ldr r0, [r3, #0]
6: 4b05 ldr r3, [pc, #20] ; (1c <notmain+0x1c>)
8: 3001 adds r0, #1
a: 6018 str r0, [r3, #0]
c: f7ff fffe bl 0 <dummy>
10: 2000 movs r0, #0
12: bc10 pop {r4}
14: bc02 pop {r1}
16: 4708 bx r1
...
Disassembly of section .data:
00000000 <x>:
0: 00000001 andeq r0, r0, r1
現在、これには情報がありませんが、何が起こっているかのアイデアを提供します。リンカーは、オブジェクトを取得し、提供された情報(この場合はflash.ld)でリンクしますデータなどが行きます。コンパイラはそのようなことを知らず、提示されたコード、リンカが接続を満たすための穴を残さなければならない外部のコードにのみ焦点を合わせることができます。データをリンクする方法を残す必要があるため、コンパイラとこの逆アセンブラーが知らないという理由だけで、すべてのアドレスはゼロに基づいています。ここには示されていないが、リンカが物を配置するために使用する他の情報があります。ここのコードは位置に依存しないので、リンカーはその仕事をすることができます。
その後、少なくともリンクされた出力の逆アセンブリが表示されます
00000020 <notmain>:
20: b510 push {r4, lr}
22: 4b05 ldr r3, [pc, #20] ; (38 <notmain+0x18>)
24: 6818 ldr r0, [r3, #0]
26: 4b05 ldr r3, [pc, #20] ; (3c <notmain+0x1c>)
28: 3001 adds r0, #1
2a: 6018 str r0, [r3, #0]
2c: f7ff fff6 bl 1c <dummy>
30: 2000 movs r0, #0
32: bc10 pop {r4}
34: bc02 pop {r1}
36: 4708 bx r1
38: 20000004 andcs r0, r0, r4
3c: 20000000 andcs r0, r0, r0
Disassembly of section .bss:
20000000 <y>:
20000000: 00000000 andeq r0, r0, r0
Disassembly of section .data:
20000004 <x>:
20000004: 00000001 andeq r0, r0, r1
コンパイラは、基本的にRAMで2つの32ビット変数を要求しています。1つは.bssにあります。初期化していないため、初期化はゼロと見なされます。もう1つは.dataです。宣言時に初期化したためです。
これらはグローバル変数であるため、他の関数がそれらを変更できると想定されています。コンパイラーは、notmainをいつ呼び出すことができるかについて何も想定していないため、表示されるもの(y = x + 1の数学)で最適化できないため、そのランタイムを実行する必要があります。RAMから2つの変数を読み取って追加し、保存し直す必要があります。
今、明らかにこのコードは機能しません。どうして?ここに示すブートストラップはnotmainを呼び出す前にRAMを準備しないため、チップが起動したときに0x20000000と0x20000004にあったゴミがyとxに使用されます。
ここでは表示しません。.dataと.bssについてのさらに長い巻き線を読んだり、ベアメタルコードでそれらを必要としない理由を読んだりできますが、他の誰かが正しくやったことを望んでいるのではなく、ツールをマスターする必要があると感じた場合。 。
https://github.com/dwelch67/raspberrypi/tree/master/bssdata
リンカスクリプトとブートストラップは多少コンパイラ固有であるため、1つのコンパイラの1つのバージョンについて学んだことはすべて、次のバージョンまたは他のコンパイラで放り出される可能性がありますが、.dataおよび.bssの準備に多大な労力を費やさない別の理由この怠け者になるために:
unsigned int x=1;
私はむしろこれをやりたい
unsigned int x;
...
x = 1;
コンパイラにそれを.textに入れてもらいます。フラッシュをそのように保存することもあれば、より多く書き込むこともあります。ツールチェーンバージョンまたは1つのコンパイラから別のコンパイラへのプログラミングおよび移植は、間違いなくはるかに簡単です。はるかに信頼性が高く、エラーが発生しにくい。はい、C標準に準拠していません。
これらの静的グローバルを作成したらどうなるでしょうか?
void dummy ( unsigned int );
static unsigned int x=1;
static unsigned int y;
int notmain ( void )
{
y = x + 1;
dummy(y);
return(0);
}
上手
00000020 <notmain>:
20: b510 push {r4, lr}
22: 2002 movs r0, #2
24: f7ff fffa bl 1c <dummy>
28: 2000 movs r0, #0
2a: bc10 pop {r4}
2c: bc02 pop {r1}
2e: 4708 bx r1
明らかに、これらの変数は他のコードでは変更できないため、コンパイラはコンパイル時に、以前と同様にデッドコードを最適化できます。
最適化されていない
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: 4804 ldr r0, [pc, #16] ; (38 <notmain+0x18>)
26: 6800 ldr r0, [r0, #0]
28: 1c40 adds r0, r0, #1
2a: 4904 ldr r1, [pc, #16] ; (3c <notmain+0x1c>)
2c: 6008 str r0, [r1, #0]
2e: f7ff fff5 bl 1c <dummy>
32: 2000 movs r0, #0
34: bd80 pop {r7, pc}
36: 46c0 nop ; (mov r8, r8)
38: 20000004 andcs r0, r0, r4
3c: 20000000 andcs r0, r0, r0
ローカル用にスタックを使用したこのコンパイラは、グローバル用にramを使用します。このデータは、.dataと.bssを適切に処理しなかったため、記述されたとおりに壊れています。
そして、分解で見ることができない最後の1つです。
:1000000000100020150000001B0000001B00000075
:100010001B00000000F004F8FFE7FEE77047000057
:1000200080B500AF04480068401C04490860FFF731
:10003000F5FF002080BDC046040000200000002025
:08004000E0FFFF7F010000005A
:0400480078563412A0
:00000001FF
xを0x12345678でpre-initに変更しました。私のリンカースクリプト(これはgnu ld用です)には、これがありません。これにより、最終的な場所をtedアドレス空間に配置したいが、tedアドレス空間のバイナリに保存し、誰かがあなたのためにそれを移動します。そして、それが起こったことがわかります。これはインテルの16進形式です。そして、我々は0x12345678を見ることができます
:0400480078563412A0
バイナリのフラッシュアドレス空間にあります。
readelfもこれを示しています
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
EXIDX 0x010040 0x00000040 0x00000040 0x00008 0x00008 R 0x4
LOAD 0x010000 0x00000000 0x00000000 0x00048 0x00048 R E 0x10000
LOAD 0x020004 0x20000004 0x00000048 0x00004 0x00004 RW 0x10000
LOAD 0x030000 0x20000000 0x20000000 0x00000 0x00004 RW 0x10000
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10
仮想アドレスが0x20000004で物理が0x48であるLOAD行