オペレーティングシステムを実行せずに、プログラムを単独でどのように実行しますか?コンピュータを起動時にロードして実行できるアセンブリプログラムを作成できますか?たとえば、フラッシュドライブからコンピュータを起動し、CPU上のプログラムを実行しますか?
オペレーティングシステムを実行せずに、プログラムを単独でどのように実行しますか?コンピュータを起動時にロードして実行できるアセンブリプログラムを作成できますか?たとえば、フラッシュドライブからコンピュータを起動し、CPU上のプログラムを実行しますか?
回答:
オペレーティングシステムを実行せずに、プログラムを単独でどのように実行しますか?
バイナリコードを、再起動後にプロセッサが検索する場所(たとえば、ARMのアドレス0)に配置します。
コンピュータが起動時にロードして実行できるアセンブリプログラムを作成できますか(たとえば、フラッシュドライブからコンピュータを起動し、ドライブ上にあるプログラムを実行します)?
質問に対する一般的な回答:実行できます。これはしばしば「ベアメタルプログラミング」と呼ばれます。フラッシュドライブから読み取るには、USBとは何かを知り、このUSBで動作するいくつかのドライバーが必要です。このドライブ上のプログラムは、特定のファイルシステム上で特定の形式にする必要もあります...これは通常、ブートローダーが行うことですが、プログラムに独自のブートローダーを含めて、ファームウェアのみが必要な場合は自己完結型にすることもできますコードの小さなブロックをロードします。
多くのARMボードでは、これらの機能のいくつかを実行できます。一部には、基本的なセットアップに役立つブートローダーがあります。
ここでは、Raspberry Piで基本的なオペレーティングシステムを実行する方法に関する優れたチュートリアルを見つけることができます。
編集:この記事とwiki.osdev.org全体で、ほとんどの質問に答え ますhttp://wiki.osdev.org/Introduction
また、ハードウェアで直接実験したくない場合は、qemuなどのハイパーバイザーを使用して仮想マシンとして実行できます。仮想化されたARMハードウェアで「hello world」を直接実行する方法については、こちらをご覧ください。
実行可能な例
OSなしで実行できるいくつかの非常に小さなベアメタルのHello Worldプログラムを作成して実行してみましょう。
より安全で開発に便利なため、QEMUエミュレーターでも可能な限り試します。QEMUテストは、QEMU 2.11.1がプリパッケージされたUbuntu 18.04ホストで行われました。
以下のすべてのx86例のコードは、このGitHubリポジトリにあります。
x86の実際のハードウェアで例を実行する方法
実際のハードウェアでサンプルを実行すると危険な場合があることを覚えておいてください。たとえば、誤ってディスクを消去したり、ハードウェアを壊したりする可能性があります。これは、重要なデータを含まない古いマシンでのみ実行してください。または、Raspberry Piなどの安価なセミディスポーザブルdevboardを使用することもできます。以下のARMの例を参照してください。
典型的なx86ラップトップの場合、次のようなことを行う必要があります。
画像をUSBスティックに書き込みます(データが破壊されます!):
sudo dd if=main.img of=/dev/sdX
USBをコンピュータに接続する
それをオン
USBから起動するように指示します。
これは、ファームウェアがハードディスクの前にUSBを選択することを意味します。
これがマシンのデフォルトの動作でない場合は、電源投入後、USBからの起動を選択できる起動メニューが表示されるまで、Enter、F12、ESCなどの奇妙なキーを押し続けます。
多くの場合、これらのメニューで検索順序を構成できます。
たとえば、T430では次のように表示されます。
オンにした後、Enterキーを押してブートメニューに入る必要があります。
次に、F12を押してUSBを起動デバイスとして選択する必要があります。
そこから、次のようにUSBを起動デバイスとして選択できます。
または、起動順序を変更してUSBの優先順位を高くし、毎回手動で選択する必要がないようにするには、[スタートアップ割り込みメニュー]画面でF1を押して、次の場所に移動します。
ブートセクター
x86で実行できる最も単純で最低レベルのことは、ブートセクターの一種であるマスターブートセクター(MBR)を作成し、それをディスクにインストールすることです。
ここでは、1回のprintf
呼び出しで作成します。
printf '\364%509s\125\252' > main.img
sudo apt-get install qemu-system-x86
qemu-system-x86_64 -hda main.img
結果:
何もしなくても、画面にはすでにいくつかの文字が印刷されていることに注意してください。それらはファームウェアによって印刷され、システムを識別するのに役立ちます。
T430では、カーソルが点滅する空白の画面が表示されます。
main.img
以下が含まれます。
\364
8進数で== 0xf4
16進数で:hlt
CPUの動作を停止するように指示する命令のエンコーディング。
したがって、プログラムは何も実行せず、開始と停止のみを行います。
\x
16進数はPOSIXで指定されていないため、8進数を使用します。
このエンコーディングは、次のようにして簡単に取得できます。
echo hlt > a.S
as -o a.o a.S
objdump -S a.o
出力:
a.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: f4 hlt
もちろん、インテルのマニュアルにも記載されています。
%509s
509スペースを生成します。バイト510までファイルに入力する必要がありました。
\125\252
8進数==の0x55
後に0xaa
。
これらは、バイト511および512でなければならない2つの必須のマジックバイトです。
BIOSは、起動可能なディスクを探してすべてのディスクを調べ、2つの魔法のバイトを持つ起動可能なディスクのみを考慮します。
存在しない場合、ハードウェアはこれを起動可能なディスクとして扱いません。
printf
マスターでない場合は、次のコマンドで内容を確認できますmain.img
。
hd main.img
これは予想されることを示しています:
00000000 f4 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 |. |
00000010 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | |
*
000001f0 20 20 20 20 20 20 20 20 20 20 20 20 20 20 55 aa | U.|
00000200
どこ20
ASCIIのスペースがあります。
BIOSファームウェアは、これらの512バイトをディスクから読み取り、メモリに入れ、PCを最初のバイトに設定して、実行を開始します。
こんにちは世界のブートセクター
最小限のプログラムを作成したので、Hello Worldに移りましょう。
明白な質問は次のとおりです:IOを行う方法?いくつかのオプション:
ファームウェア(BIOSやUEFIなど)に依頼してください
VGA:書き込まれると画面に出力される特別なメモリ領域。保護モードで使用できます。
ドライバーを作成し、ディスプレイハードウェアと直接通信します。これは「適切な」方法です。より強力ですが、より複雑です。
シリアルポート。これは、ホスト端末との間で文字を送受信する非常にシンプルな標準プロトコルです。
デスクトップでは、次のようになります。
ソース。
残念ながら、ほとんどの最新のラップトップでは公開されていませんが、開発ボードの一般的な方法です。以下のARMの例を参照してください。
このようなインターフェイスは、たとえばLinuxカーネルのデバッグに非常に役立つため、これは本当に残念です。
チップのデバッグ機能を使用します。ARMは、たとえば、セミホスティングを呼び出します。実際のハードウェアでは、追加のハードウェアとソフトウェアのサポートが必要ですが、エミュレータでは無料で便利なオプションになります。例。
ここでは、x86の方が簡単なBIOSの例を実行します。ただし、これが最も堅牢な方法ではないことに注意してください。
main.S
.code16
mov $msg, %si
mov $0x0e, %ah
loop:
lodsb
or %al, %al
jz halt
int $0x10
jmp loop
halt:
hlt
msg:
.asciz "hello world"
link.ld
SECTIONS
{
/* The BIOS loads the code from the disk to this location.
* We must tell that to the linker so that it can properly
* calculate the addresses of symbols we might jump to.
*/
. = 0x7c00;
.text :
{
__start = .;
*(.text)
/* Place the magic boot bytes at the end of the first 512 sector. */
. = 0x1FE;
SHORT(0xAA55)
}
}
組み立ててリンクする:
as -g -o main.o main.S
ld --oformat binary -o main.img -T link.ld main.o
qemu-system-x86_64 -hda main.img
結果:
T430の場合:
テスト済み:Lenovo Thinkpad T430、UEFI BIOS 1.16。Ubuntu 18.04ホストで生成されたディスク。
標準のユーザーランドアセンブリの指示に加えて、次のものがあります。
.code16
:16ビットコードを出力するようにGASに指示します
cli
:ソフトウェア割り込みを無効にします。それらは、プロセッサを再び実行し始める可能性がありますhlt
int $0x10
:BIOS呼び出しを行います。これは、文字を1つずつ印刷するものです。
重要なリンクフラグは次のとおりです。
--oformat binary
:通常のユーザーランド実行可能ファイルの場合のように、生のバイナリアセンブリコードを出力します。ELFファイル内にラップしないでください。リンカスクリプト部分をよりよく理解するには、リンクの再配置手順に慣れておきます。リンカは何をしますか?
Cooler x86ベアメタルプログラム
以下に、私が達成したいくつかのより複雑なベアメタルセットアップを示します。
アセンブリの代わりにCを使用
概要:GRUBマルチブートを使用してください。これは、あなたが考えたことのない多くの迷惑な問題を解決します。以下のセクションを参照してください。
x86の主な問題は、BIOSがディスクからメモリに512バイトしかロードしないことです。Cを使用すると、これらの512バイトが爆発する可能性があります。
これを解決するには、2段階のブートローダーを使用します。これにより、BIOS呼び出しがさらに行われ、ディスクからメモリに読み込まれるバイト数が増えます。以下は、int 0x13 BIOS呼び出しを使用した、最初からの最小限のステージ2アセンブリの例です。
または:
-kernel
オプションを使用して、ELFファイル全体をメモリにロードします。以下は、そのメソッドで作成したARMの例です。kernel7.img
QEMUと同様に、デフォルトのファームウェアが、という名前のELFファイルからのイメージのロードを処理-kernel
します。教育目的のみのために、これは1段階の最小限のCの例です。
main.c
void main(void) {
int i;
char s[] = {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};
for (i = 0; i < sizeof(s); ++i) {
__asm__ (
"int $0x10" : : "a" ((0x0e << 8) | s[i])
);
}
while (1) {
__asm__ ("hlt");
};
}
entry.S
.code16
.text
.global mystart
mystart:
ljmp $0, $.setcs
.setcs:
xor %ax, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %ss
mov $__stack_top, %esp
cld
call main
リンカー.ld
ENTRY(mystart)
SECTIONS
{
. = 0x7c00;
.text : {
entry.o(.text)
*(.text)
*(.data)
*(.rodata)
__bss_start = .;
/* COMMON vs BSS: /programming/16835716/bss-vs-common-what-goes-where */
*(.bss)
*(COMMON)
__bss_end = .;
}
/* /programming/53584666/why-does-gnu-ld-include-a-section-that-does-not-appear-in-the-linker-script */
.sig : AT(ADDR(.text) + 512 - 2)
{
SHORT(0xaa55);
}
/DISCARD/ : {
*(.eh_frame)
}
__stack_bottom = .;
. = . + 0x1000;
__stack_top = .;
}
走る
set -eux
as -ggdb3 --32 -o entry.o entry.S
gcc -c -ggdb3 -m16 -ffreestanding -fno-PIE -nostartfiles -nostdlib -o main.o -std=c99 main.c
ld -m elf_i386 -o main.elf -T linker.ld entry.o main.o
objcopy -O binary main.elf main.img
qemu-system-x86_64 -drive file=main.img,format=raw
C標準ライブラリ
ただし、C標準ライブラリも使用したい場合は、POSIXを介してC標準ライブラリの機能の多くを実装するLinuxカーネルがないため、物事はさらに楽しくなります。
Linuxのような本格的なOSに行かなくても、いくつかの可能性があります。
自分で書いてください。結局のところ、ヘッダーとCファイルの集まりですよね?正しい??
詳細な例:https : //electronics.stackexchange.com/questions/223929/c-standard-libraries-on-bare-metal/223931
NEWLIB用具あなたのためのすべての退屈の非OSの特定のもの、例えばmemcmp
、memcpy
など
次に、必要なシステムコールを実装するためのスタブをいくつか提供します。
たとえば、次のexit()
セミホスティングを使用してARMに実装できます。
void _exit(int status) {
__asm__ __volatile__ ("mov r0, #0x18; ldr r1, =#0x20026; svc 0x00123456");
}
この例に示すように。
たとえばprintf
、UARTまたはARMシステムにリダイレクトしたりexit()
、セミホスティングを実装したりできます。
FreeRTOSやZephyrなどの組み込みオペレーティングシステム。
このようなオペレーティングシステムでは、通常、プリエンプティブスケジューリングをオフにできるため、プログラムの実行時間を完全に制御できます。
これらは、事前に実装された一種のNewlibと見なすことができます。
GNU GRUBマルチブート
ブートセクターは単純ですが、あまり便利ではありません。
これらの理由により、GNU GRUBはマルチブートと呼ばれるより便利なファイル形式を作成しました。
最小限の動作例:https : //github.com/cirosantilli/x86-bare-metal-examples/tree/d217b180be4220a0b4a453f31275d38e697a99e0/multiboot/hello-world
また、GitHubのサンプルリポジトリでも使用して、USBを100万回書き込むことなく、実際のハードウェアですべてのサンプルを簡単に実行できるようにしています。
QEMUの結果:
T430:
OSをマルチブートファイルとして準備すると、GRUBは通常のファイルシステム内でそれを見つけることができます。
これは、ほとんどのディストリビューションがOSイメージをに配置すること/boot
です。
マルチブートファイルは基本的に、特別なヘッダーを持つELFファイルです。それらはhttps://www.gnu.org/software/grub/manual/multiboot/multiboot.htmlにある GRUBによって指定されています。
を使用して、マルチブートファイルを起動可能なディスクに変換できますgrub-mkrescue
。
ファームウェア
実際、ブートセクターは、システムのCPUで実行される最初のソフトウェアではありません。
最初に実際に実行されるのは、ソフトウェアであるいわゆるファームウェアです。
よく知られているファームウェアは次のとおりです。
ファームウェアは次のようなことを行います:
起動可能なものが見つかるまで、各ハードディスク、USB、ネットワークなどをループします。
QEMUを実行する-hda
と、それmain.img
はハードウェアに接続されたハードディスクでhda
あり、最初に試行されたものであり、それが使用されます。
最初の512バイトをRAMメモリアドレス0x7c00
にロードし、CPUのRIPをそこに配置して実行します
ブートメニューやBIOSプリントコールなどをディスプレイに表示する
ファームウェアは、ほとんどのOSが依存するOSのような機能を提供します。たとえば、PythonサブセットはBIOS / UEFIで実行するように移植されています:https : //www.youtube.com/watch?v=bYQ_lq5dcvM
ファームウェアはOSと区別がつかず、そのファームウェアは、実行できる唯一の「真の」ベアメタルプログラミングであると主張できます。
難しい部分
PCの電源を投入しても、チップセットを構成するチップ(ノースブリッジ、サウスブリッジ、SuperIO)はまだ適切に初期化されていません。BIOS ROMはCPUから可能な限り遠くにありますが、CPUからアクセスできる必要があります。そうしないと、CPUが実行する命令を持たなくなります。これは、BIOS ROMが完全にマップされていることを意味するものではなく、通常はマップされていません。しかし、起動プロセスを実行するのに十分なだけマッピングされています。他のデバイスは、忘れてください。
QEMUでCorebootを実行する場合、Corebootの上位レイヤーとペイロードを試すことができますが、QEMUは低レベルのスタートアップコードを試す機会がほとんどありません。まず、RAMは最初から正しく機能します。
BIOSの初期状態をポスト
ハードウェアの多くの事柄と同様に、標準化は弱く、コードをBIOSの後で実行し始めたときの、レジスターの初期状態は信頼できません。
だから、自分に好意をして、次のような初期化コードを使用してください:https : //stackoverflow.com/a/32509555/895245
以下のようなレジスタ%ds
とは%es
、あなたが明示的にそれらを使用していない場合でも、それらをゼロにする必要がありますので、重要な副作用を持っています。
一部のエミュレーターは実際のハードウェアよりも優れていて、初期状態が良いことに注意してください。次に、実際のハードウェアで実行すると、すべてが壊れます。
エルトリト
CDに書き込むことができる形式:https : //en.wikipedia.org/wiki/El_Torito_%28CD-ROM_standard%29
ISOまたはUSBで動作するハイブリッドイメージを生成することもできます。これはgrub-mkrescue
(例)で行うことができ、Linuxカーネルでmake isoimage
を使用して行うこともできますisohybrid
。
腕
ARMでは、一般的な考え方は同じです。
私たちがIOに使用するためのBIOSのような広く利用可能な半標準化されたプリインストールされたファームウェアはありません。
アップロードしました:
GitHubにある簡単なQEMU C + Newlibと生のアセンブリの例。
prompt.c例例えばあなたのホスト端末から入力を受け取り、すべてのシミュレートされたUART経由バック出力が得られます。
enter a character
got: a
new alloc of 1 bytes at address 0x0x4000a1c0
enter a character
got: b
new alloc of 2 bytes at address 0x0x4000a1c0
enter a character
完全に自動化されたRaspberry Piブリンカーのセットアップ:https : //github.com/cirosantilli/raspberry-pi-bare-metal-blinker
参照:Raspberry PiでOSなしでCプログラムを実行する方法?
QEMUのLEDを「確認」するには、デバッグフラグを使用してソースからQEMUをコンパイルする必要があります。https://raspberrypi.stackexchange.com/questions/56373/is-it-possible-to-get-the-state-of- the-leds-and-gpios-in-a-qemu-emulation-like-t
次に、UART hello worldを試してみてください。ブリンカーの例から開始して、カーネルを次の例で置き換えることができます。https://github.com/dwelch67/raspberrypi/tree/bce377230c2cdd8ff1e40919fdedbc2533ef5a00/uart01
最初に私が説明したように、UARTをRaspbianで動作させる:https : //raspberrypi.stackexchange.com/questions/38/prepare-for-ssh-without-a-screen/54394#54394これは次のようになります:
必ず正しいピンを使用してください。そうしないと、UARTからUSBへのコンバーターを焼くことができます。アースと5Vを短絡することで、すでに2回実行しています...
最後に、ホストからシリアルに接続します:
screen /dev/ttyUSB0 115200
Raspberry Piの場合、USBスティックの代わりにマイクロSDカードを使用して実行可能ファイルを格納します。実行可能ファイルには通常、コンピューターに接続するためのアダプターが必要です。
/ubuntu/213889/microsd-card-is-set-to-read-only-state-how-can-i-write-dataに示すように、SDアダプターのロックを解除することを忘れないでください-on-it / 814585#814585
https://github.com/dwelch67/raspberrypiは、現在利用可能な最も人気のあるベアメタルRaspberry Piチュートリアルのようです。
x86とのいくつかの違いは次のとおりです。
IOが直接魔法のアドレスに書き込むことによって行われますが、何もありませんin
とout
指示。
Raspberry Piなどの一部の実際のハードウェアでは、自分でファームウェア(BIOS)をディスクイメージに追加できます。
それはファームウェアの更新をより透過的にするので、それは良いことです。
資源
オペレーティングシステムは、プログラムである、我々はまたできるので、ゼロから作成または変更することによって、私たち自身のプログラムを作成するの1の特徴(制限または追加)小さなオペレーティングシステム、およびその後、ブートプロセスの間にそれを実行します(使用してISOイメージを) 。
たとえば、このページを出発点として使用できます。
ここでは、オペレーティングシステム全体が512バイトのブートセクター(MBR)に完全に適合しています。
そのようなまたは類似した単純なOSを使用して、以下を可能にする単純なフレームワークを作成できます。
作るRAMにディスク上のブートローダーのロード以降のセクタを、そして実行を継続するために、そのポイントにジャンプ。または、フロッピードライブで使用されるファイルシステムであるFAT12を読み取り、それを実装することもできます。
ただし、多くの可能性があります。たとえば、より大きなx86アセンブリ言語OSを確認するには、よくコメントされたコードと広範なドキュメントを備えたシンプルな16ビットリアルモードOSの動作を示す学習ツールであるx86オペレーティングシステムであるMykeOSを探索します。
オペレーティングシステムなしで実行される他の一般的な種類のプログラムもブートローダーです。このようなコンセプトにインスパイアされたプログラムを、たとえばこのサイトを使用して作成できます。
上記の記事は、そのようなプログラムの基本的なアーキテクチャも示しています。
- 0000:7C00アドレスによるメモリへの正しい読み込み。
- 高水準言語で開発されたBootMain関数を呼び出す。
- 「” Hello、world…”を低レベルから」というメッセージをディスプレイに表示します。
ご覧のとおり、このアーキテクチャは非常に柔軟であり、必ずしもブートローダーではなく、任意のプログラムを実装できます。
具体的には、それを使用する方法を示し、「混合コード」手法が可能であるおかげで、高レベルの構造を組み合わせること(からC又はC ++)、低レベルのコマンドで(からアセンブラ)。これは非常に便利な方法ですが、次のことを覚えておく必要があります。
プログラムをビルドして実行可能ファイルを取得するには、16ビットモードのアセンブラーのコンパイラーとリンカーが必要です。C / C ++の場合、16ビットモードのオブジェクトファイルを作成できるコンパイラのみが必要です。
この記事では、作成されたプログラムの動作を確認する方法と、テストとデバッグを実行する方法も示しています。
上記の例では、セクターMBRをデータメディアにロードするという事実を使用しました。ただし、たとえばUEFIアプリケーションを使ってプレーティングすることで、さらに深く掘り下げることができます。
OSをロードするだけでなく、UEFIはEFIシステムパーティション上のファイルとして存在するUEFIアプリケーションを実行できます。これらは、UEFIコマンドシェルから、ファームウェアのブートマネージャーによって、または他のUEFIアプリケーションによって実行できます。UEFIアプリケーションは、システムメーカーとは関係なく開発およびインストールできます。
UEFIアプリケーションの一種は、GRUB、rEFInd、Gummiboot、Windows Boot ManagerなどのOSローダーです。OSファイルをメモリにロードして実行します。また、OSローダーは、実行する別のUEFIアプリケーションを選択できるユーザーインターフェイスを提供できます。UEFIシェルなどのユーティリティもUEFIアプリケーションです。
そのようなプログラムの作成を開始したい場合は、たとえば、次のWebサイトから始めることができます。
EFIのプログラミング:「Hello、World」プログラムの作成 / UEFIプログラミング-最初のステップ
オペレーティングシステムが起動する前に実行されている悪意のあるソフトウェア(プログラム)のグループ全体があることはよく知られています。
それらの巨大なグループは、上記のすべてのソリューションと同様に、MBRセクターまたはUEFIアプリケーションで動作しますが、ボリュームブートレコード(VBR)やBIOSなどの別のエントリポイントを使用するものもあります。
少なくとも4つの既知のBIOS攻撃ウイルスがあり、そのうち2つはデモンストレーション用です。
またはおそらく別のものも。
ブートキットは、概念実証の開発から大量配布に進化し、現在は事実上オープンソースソフトウェアになっています。
また、このコンテキストでは、オペレーティングシステム(またはこのための実行可能プログラム)を起動するさまざまな形式があることにも言及する価値があると思います。そこに多くがありますが、私は注意を払うしたいネットワークからコードをロードするネットワークブートオプション(使用してPXE私たちは、コンピュータ上のプログラムを実行することができます)、かかわらず、そのオペレーティングシステムのもかかわらず、任意の記憶媒体のありますコンピュータに直接接続: