しかし、完全64bit対応というのも難しいものです。32bit値でもいいのにわざわざ64bitをつかうと32bit環境で速度が低下しますし、
だからといって32bit値を使おうとするとその境界で型キャストが何回もいるのでコンパイルワーニングがうるさいです。
で、スクリプトを使うために仮想マシンを組もうとするとどちらかで決めないとセーブ、ロード動作がおかしくなるので決めうちしますが、
そのときのキャスト処理がとんでもないことになるのが・・・。
今回はIMAGE_SECTION_HEADERを扱います。これはIMAGE_NT_HEADERSの後ろについているものです。
プログラムには「セクション」という概念があります。これは同じ役割を持つデータを集めて一つの仮想メモリの領域を考え(それをセクションとして扱い)、
それらの集合体としてプログラムを考えるという発想によるものです。
この概念を使うことで、それぞれのメモリ領域のメモリ保護を簡単に行うことができることと、データを扱いやすくなるという利点があります。
セクションの数は前に出てきたIMAGE_FILE_HEADERのNumberOfSectionsメンバで示されます。
この数だけIMAGE_NT_HEADERSの後ろにIMAGE_SECTION_HEADER構造体が配列として存在するというメモリ状態になります。
実行ファイルを操作するときには必ずと言っていいほど操作を行う領域なのでちょっとみてみましょう。
- Name
セクション名を格納する領域です。通常は8byteの領域です。
コンパイラやセクションを構築するプログラムによって自由につけてもいい領域ですが、何かはわかるように書くのが一般的です。たとえば
.text | VisualC++系で構築したときのコード領域 |
CODE | BorlandC系で構築したときのコード領域 |
UPX1 | UPXの自己圧縮を使ったときの自己解凍コード領域 |
のような感じです。
- VirtualSize
この領域はunionになっていますが、通常はこちらしか使いません。
対象セクションを仮想メモリ上に展開したときのセクションのメモリサイズを示します。
- VirtualAddress
対象セクションを仮想メモリ上に展開したときの開始アドレスをロードメモリ相対で示します。
- SizeOfRawData
ファイル上にある仮想メモリにロードされるべきデータのサイズを示します。
ファイルからこのサイズだけ読み取られて仮想メモリ上に展開されます。
未初期化データセクション(仮想メモリだけを確保するセクション)であるときは0になります。
- PointerToRawData
ファイル上にある仮想メモリにロードされるべきデータのファイル上での先頭位置を示します。
未初期化データセクションであるときは0になります。
- Characteristics
このセクションの「初期仮想メモリ確保時」の種別フラグを示します。
実行中にセクションのメモリ保護状態などが変更してもこのフラグを変更する必要はありません。
以下のフラグが有効になります。(よく使うものだけ)
フラグ名 | フラグの意味 |
---|---|
IMAGE_SCN_CNT_CODE | 実行用コード領域になります。 |
IMAGE_SCN_CNT_INITIALIZED_DATA | 初期化済みデータ領域になります。 |
IMAGE_SCN_CNT_UNINITIALIZED_DATA | 未初期化データ領域になります。 |
IMAGE_SCN_MEM_NOT_CACHED | このセクションをメモリ上にキャッシュしません。VirtualAlloc系のPAGE_NOCACHEに相当します。 |
IMAGE_SCN_MEM_NOT_PAGED | このセクションをページファイル上におかないようにします。 |
IMAGE_SCN_MEM_EXECUTE | このセクションを実行可能として確保します。VirtualAlloc系のPAGE_EXECUTE系属性を持ちます。 |
IMAGE_SCN_MEM_READ | このセクションを読み込み可能として確保します。VirtualAlloc系のPAGE_READ系属性を持ちます。 |
IMAGE_SCN_MEM_WRITE | このセクションを書き込み可能として確保します。VirtualAlloc系のPAGE_WRITE系属性を持ちます。 |
一番重要なのはCharacteristicsメンバだったりします。この領域はなぜか各プログラムのランタイムから参照されることがあるようで、
ちゃんと実行可能な領域は仮想メモリの状態だけではなくこのメンバのフラグも正しく更新しないとプログラムエラーとなっちゃうことが多々あります。
また、仮想メモリを自前で確保するときはできるだけこのフラグのメモリ状態を設定するようにします。でないとDEPやらいろいろなチェックに引っかかります。
また、IMAGE_SCN_CNT_CODEをつけなくてもIMAGE_SCN_MEM_EXECUTEのフラグがあるならそのセクションは実行可能です。なのでその部分に実行用コードをおくこともできます。
自己圧縮形ではこのような形にすることもありますが、あまりそのような状態にするのはおすすめしませんが・・・。
ちなみに、VisualC++系でプログラムを作るとセクションはデフォルトではこんな感じになります。
セクション名 | メモリ保護状態 | セクション領域の使用状態 |
---|---|---|
.text | CODE + EXECUTE + READ | 実行用コード領域 |
.rdata | INIT + READ | プログラム内の固定文字列や定数などの読み込みのみのメモリ領域 |
.data | INIT + READ + WRITE | static変数やグローバル変数、クラス情報などの共有変数領域 |
.rsrc | INIT + READ | リソースデータ領域 |
DLLのインポート情報なども.rdataにあることが多いです。(初期化したときはローダにより一時的にWRITE属性が与えられるため)
ということで駆け足でWindowsの実行ファイルのヘッダデータをみてきました。
ちょっと複雑な構造をしていますが、すべての実行ファイル(exeやdllだけではなくsysなどのドライバも)にあるものなので覚えておくとちょっと使えます。
次回からそれぞれのセクションのデータについてふれてみましょう。