連載
|
|
Page1
Page2
|
Windowsローダーがアプリケーションを起動する処理プロセス
それではWindowsローダーの処理を1つずつ順に追っていくことにしよう。
●Windowsローダーによる「PEヘッダ」の解析
Windowsローダーは最初に、PEフォーマットで作成された実行可能ファイルのヘッダ部分、つまり「PEヘッダ」を調べて、どのような起動手順を踏めばよいかを理解する。従ってまずは、このPEヘッダの中身を調べてみよう。
私たち人間がPEフォーマットの内容を参照するためのツールとして、「dumpbin.exe」というコマンドライン・ツールがある。本稿ではこれを用いてPEヘッダの内容を解析する。なおdumpbin.exeは、マイクロソフトが提供する統合開発環境のVisual Studio .NETやVisual C++ .NETなどに付属するツールの1つなので、実際に読者諸氏がこのdumpbin.exeを使うには、これらの開発環境のいずれかがコンピュータ上にあらかじめインストールされている必要がある。
PEヘッダ情報を確認するには、dumpbin.exeのパラメータ(=引数)に、実行可能ファイルを指定し(本稿の例では「WindowsApplication1.exe」)、さらに「/header」オプションを付けて実行すればよい。これを、実際に実行しているのが次の画面である。
実行可能ファイル「WindowsApplication1.exe」のPEヘッダの内容 | |||||||||
「WindowsApplication1.exe」という実行可能ファイルのPEヘッダを表示したところ。Visual Studio .NET 2003がインストールされた環境で、[スタート]メニューの[すべてのプログラム]−[Microsoft Visual Studio .NET 2003]−[Visual Studio .NET ツール]−[Visual Studio .NET 2003 コマンド プロンプト]を選択すれば、コマンド・プロンプトが表示される。このコマンド・プロンプトには、Visual Studio .NET関連ツールにパスが通っているため、開発関連のツールにフル・パスを指定しなくても、ツール名だけでアクセス(=利用)できるようになる。 | |||||||||
|
上の画面のPEヘッダ情報で表示されている各数値は、基本的に16進数*4で記述されている。なお以降の説明では、16進数の値には、基本的に「0x」というプレフィックス(=頭文字)を付けて表現する(例えば、「0x400000」)。
*4 以降の説明を理解するには、16進数を読める必要がある。16進数の数え方を学ぶには、「@IT自分戦略研究所」の「コンピュータの基本、2進数を理解しよう」がお勧めである。 |
次に、このPEヘッダの情報から解析・確認できる次の2点の内容を追いかけてみよう。
- のメイン・メモリ上にロードされたプログラム・コードの先頭位置(と範囲)
- のエントリ・ポイント(=実行開始位置)に記述されているプログラム・コード
●Windowsローダーによるメイン・メモリ上へのプログラム・データのロード
まず1の方は、PEヘッダの「OPTIONAL HEADER VALUES」というセクションの「image base」という項目を見れば分かる。本稿の例では、この項目は次のようになっている。
OPTIONAL HEADER VALUES |
「image base」には、メイン・メモリ上にロードされるときのプログラム・コードの先頭位置のアドレス(=メモリ上の番地*5)が示される。従って本稿の例では、メモリ上のアドレス「0x400000番地」を起点として(「0x407FFF番地」のアドレスまでの範囲に)、プログラム・コードがロードされるということが、この「image base」の情報から読み取れる。
*5 ただしこれはプログラマから見えるメモリ空間の「仮想アドレス」で、実際の「物理アドレス」ではない。 |
メイン・メモリ上へのロードされるときのプログラム・コードの先頭位置(とその範囲) |
●Windowsローダーによるエントリ・ポイントへのアクセス
次に2の方は、PEヘッダの「OPTIONAL HEADER VALUES」セクションの「entry point」項目に記されている。本稿の例では、次のようになっている。
OPTIONAL HEADER VALUES |
「entry point」項目には、プログラム実行の開始地点(=プログラム実行のエントリ・ポイント)のメモリ上のアドレスが値として表示される(この例では「0x3A2E」)。ただしこのアドレス値は、先ほどの「image base」のアドレスを基準にした相対的なアドレス値である*6。従って、本稿の例でいえば、このentry pointの「0x3A2E」という値と、image baseの「0x400000」という値を足し合わせた、「0x403A2E番地」が真のエントリ・ポイントのアドレスということになる。
*6 このような「相対的な仮想アドレス」は、「RVA:Relative Virtual Address」と呼ばれる。 |
プログラム・コードの実行開始地点「エントリ・ポイント」の位置 |
●エントリ・ポイントに記述されているプログラム・コードの内容
Windows OSは、プログラムを起動開始するために、エントリ・ポイントのアドレス(本稿の例では、「0x403A2E」)に記載されているプログラム・コードを呼び出す。
それでは、エントリ・ポイントのアドレスには、一体どのようなプログラム・コードが書かれているのだろうか? もちろんこれは、実行するアプリケーションによって、さまざまに異なるわけだが、.NETアプリケーションでは、このエントリ・ポイントの部分のプログラム・コードが(基本的に)固定的な内容となっている。実際にはどのような内容になっているのか、そのプログラム・コードの中身を調べてみよう。
メモリ上にロードされるときのプログラム・コードの内容を調べるには、先ほども利用したdumpbin.exeに、実行可能ファイルを指定し(本稿の例では「WindowsApplication1.exe」)、さらに「/rawdata」オプションを付けて実行すればよい。これを実際に実行しているのが次の画面である。
「WindowsApplication1.exe」のエントリ・ポイント部分のプログラム・コードの内容 | ||||||
この例では、「dumpbin.exe WindowsApplication1.exe /rawdata:1,16 | more」というコマンドを入力・実行して、メモリ上にロードされるときのプログラム・コードの内容を参照している(実はこれは、単にファイル内容をダンプ(=出力)しているだけで、左端にあるアドレス表示がメモリ上にロードされるときのアドレスとなっているだけだ。実際のメモリ上の内容を参照しているわけではないので注意すること)。なお、「/rawdata」オプションの後に続く、「:1」は1byte(=8bits)ずつのバイト列を出力するという指定を意味する。これによりexeファイルの内容が16進数2けた(「00」〜「FF」)の並びにより表示される。また「,16」は、1行の中に、16進数2けたの値が何個並ぶのかを指定している(この例では、「16」列を指定しているので、「00」「FF」といった数値が16個、横に並ぶことになる)。最後に「 | more」というコマンドが付加されているが、これはページ単位で出力を表示させるためのものである(これを付けないと、全内容が一気に出力されてしまい、目的のアドレス部分を確認できない)。 | ||||||
|
上の画面の内容から、エントリ・ポイント部分のプログラム・コードは、次のような6bytesのコードで成り立っていることが分かる。
FF 25 00 20 40 00 |
このような数値の並び(「機械語」と呼ばれる)を見ただけで、その意味まで読み取ることは難しい(もちろんコンピュータにとっては簡単だが……)。そこで、このようなプログラム・コードを人間が容易に記述したり、読んだりするために、機械語の「数値の並び」が持つ意味(例えば、CPUの処理命令など)に対して「名前」を割り当てた「アセンブリ言語」というものが存在する。
ということで、上記の機械語のプログラム・コードを、アセンブリ言語のプログラム・コードへ置き換えてみよう。そうすれば、このプログラム・コードの意味がはっきりするはずだ。
まず、先頭の「FF 25」という機械語は、アセンブリ言語の「JMP <32bitsアドレス>」という構文に置き換えられる。この構文に従えば後半に32bitsアドレス(=4bytes)が続くはずなので、残りの数値である「00 20 40 00」は、そのような4bytesからなる1つのアドレス値に置き換えなければならない。ここで注意が必要である。x86系のCPUアーキテクチャでは、メモリへのデータ格納方法として、「リトルエンディアン」と呼ばれる手法が用いられている。このリトルエンディアン方式では、最下位のバイト(byte)から1つずつ上にさかのぼりながらデータを格納する。つまり、「00 20 40 00」という数値の並びは、最下位のバイトから順に読み取って、「00 40 20 00」→「0x00402000」というアドレスになる。
以上の置き換え内容をまとめると、6bytesのコードは次のようなアセンブリ言語の命令文と同等である。
JMP 0x00402000 |
これは「0x00402000番地のアドレスへジャンプ(=移動)せよ」という処理命令を意味する。よって、ジャンプ先のアドレス「0x00402000」にあるプログラム・コードが次に実行されることになる。
それでは、このジャンプ先のアドレスには、どのようなプログラム・コードが存在するのだろうか? 結論からいうと、ジャンプ先にあるプログラム・コードは、すでに.NETの実行環境の領域に入る。
これが意味することは、.NETアプリケーションの起動処理においては、「Win32アプリケーションとして実行されるプログラム・コード(以降、「アンマネージ・コード」)は、たった6bytesしかない」ということだ。それ以外のすべてのプログラム・コードは、.NETアプリケーションとして実行されるプログラム・コード(以降、「マネージ・コード」)である。
しかも、Windows XP/Windows Server 2003以降では、Windows OS自体が.NETアプリケーションの存在と起動手順を熟知しているため、いま述べた6bytesのプログラム・コードの実行はスキップして、直接、.NETの領域部分にジャンプして、.NETアプリケーションのプログラム・コードから実行を開始する。つまりこれらのOSでは、完全に.NET環境のみで実行されるのである。
●処理命令の順次実行によるプログラムの実行開始
最後に、先ほど示したジャンプ先のアドレス「0x00402000」に、何があるのかを確かめて今回は終わりにしよう。
これを説明する前に、次のことを確認していただきたい。
それは、Win32アプリケーションでは、ファイル内のプログラム・コードだけでなく、別ファイルの、具体的にはDLLファイル(.dllファイル。ライブラリ)のプログラム・コードも利用することができる、ということだ。
このような外部のDLLファイルの使用に関する情報(以降、「インポート情報」)は、あらかじめPEフォーマットのファイル内にデータとして格納されている。
そこで、再度コマンドライン・ツール「dumpbin.exe」に登場してもらい、このインポート情報を参照してみよう。これを行うには、dumpbin.exeのパラメータに.NETアプリケーションを指定し(本稿の例では「WindowsApplication1.exe」)、さらに「/imports」オプションを付けて実行する。これを実際に実行しているのが、次の画面である。
実行可能ファイル「WindowsApplication1.exe」のインポート情報の内容 | ||||||
この画面では、「dumpbin.exe WindowsApplication1.exe /imports」というコマンドを入力して、実行可能ファイル「WindowsApplication1.exe」のインポート情報の内容を参照している。なお「/imports」オプションは、実行可能ファイルが使用する(=インポートする)DLLファイルの一覧情報(=インポート情報)を表示するためのものである。 | ||||||
|
この画面を見ると、アドレス「0x00402000」に、「mscoree.dll」というDLLファイルのインポート情報が存在し、さらにそのインポート情報のインデックス「0番」の場所に、「_CorExeMain」という関数が登録されているのが分かる。つまり、先ほどのプログラムのエントリ・ポイントにあったプログラム・コードが実行されると、このインポート情報のアドレス「0x00402000」まで飛んできて、mscoree.dllファイルにある_CorExeMain関数を呼び出すというわけである(なお、この呼び出し先である_CorExeMain関数のアドレスは、実行時に動的に決定される)。
実はこの「mscoree.dll」は、<.NET Framework>によりインストールされるDLLファイルの1つで、.NETが動作する環境ならば、Windows OSのシステム・ディレクトリ「%Systemroot%\System32」に配置されているはずだ。
従って、.NET Frameworkが提供するmscoree.dllの中に含まれる、この_CorExeMain関数こそが、「.NETアプリケーションが.NETとして動作し始める際の最初のエントリ・ポイント」なのである。ちなみに、DLLファイル(.dllファイル)の場合の.NETプログラムとしてのエントリ・ポイントは、mscoree.dllファイルの_CorDllMain関数である(DLLファイルの実行手順については本連載では割愛するが、本稿の手順により調べていけば、それを確かめることができるだろう)。
■
今回はWindows OS上でアプリケーションが起動する仕組みを順を追って解説した。しかし、今回紹介した仕組みは、.NETアプリケーションが.NET Frameworkの実行環境である「CLR」上で動作し始める前の処理段階でしかない。_CorExeMain関数が呼び出された後から、初めて実際に<.NETアプリケーション>として動作し始めるわけだが、その仕組みついては次回、解説する。
INDEX | ||
.NETの動作原理を基礎から理解する! | ||
第2回 .NETアプリケーションが起動する仕組み | ||
1.Windows OSという視点から見た場合のプログラム実行の仕組み | ||
2.Windowsローダーがアプリケーションを起動する処理プロセス | ||
「.NETの動作原理を基礎から理解する!」 |
- 第2回 簡潔なコーディングのために (2017/7/26)
ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている - 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう - 第1回 明瞭なコーディングのために (2017/7/19)
C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える - Presentation Translator (2017/7/18)
Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
|
|