(興味深いことに、これらの並外れた能力を知っているホリボルスキキは、「オペレーティングシステムではなく、グラフィカルシェルでした」という既存のホリボルシキ?)
そして、彼女はどのように管理しましたか?
データ管理
リアルタイムWindowsにはスワップはありませんでした。 不変データ(リソースなど)はメモリから削除され、必要に応じて実行可能ファイルから再度読み込まれました。 可変データはアンロードできませんでしたが、(他のデータと同様に)移動できました。メモリブロックを操作するアプリケーションはアドレスを使用せず、ハンドルを使用します。 また、データにアクセスするときは、ブロックを「修正」してアドレスを取得し、必要に応じてWindowsが移動できるように「解放」します。 十数年後に.NETに似たものが登場し、すでにピン止めと呼ばれていました。
関数
GlobalLock
/
GlobalUnlock
および
LockResource
/
FreeResource
は、メモリブロック(リソースを含む)がWin32APIで移動することはありませんでしたが、これらの古代との互換性のために
FreeResource
に保持されました。
LockSegment
および
UnlockSegment
(ハンドルではなく、アドレスでメモリを
UnlockSegment
/解放する)は、「廃止、使用しない」とマークされたドキュメントにしばらく残っていましたが、メモリが残っていません。
長期間メモリを修正する必要がある人のために、
GlobalWire
機能もありました-「ブロックがアドレス空間の中央に
GlobalWire
ないように、メモリの下端に移動して修正します」。
GlobalUnwire
と一致し、
GlobalUnwire
と完全に同等です。 このペアの関数は、驚いたことに、kernel32.dllでまだ機能していますが、ドキュメントからは既に削除されています。 現在は、
GlobalLock
/
GlobalUnlock
です。
保護モードのWindowsでは、
GlobalLock
機能
GlobalLock
「スタブ」
GlobalLock
置き換えられました。Windowsは、アプリケーションに表示される「仮想アドレス」を変更せずにメモリブロックをシャッフルできるようになりました(セレクター:オフセット)-これは、アプリケーションがアップロードできないオブジェクトを修正する必要がなくなったことを意味します。 言い換えれば、ピン留めはブロックのアンロードを防止しますが、(アプリケーションには見えない)ブロックの移動は防止しません。 したがって、物理メモリ内のデータを「実際に」修正するために、それだけが必要な人(たとえば、外部デバイスで作業するため)に、
GlobalFix
/
GlobalUnfix
ペアを追加しました。
GlobalWire
/
GlobalUnwire
と同様に、Win32ではこれらの関数は役に立たなくなりました。 また、同じようにドキュメントから削除されますが、kernel32.dllに残り、
GlobalLock
/
GlobalUnlock
GlobalLock
GlobalUnlock
。
コード管理
最も難しいのはここから始まります。 コードのブロック-不変データ-はメモリから削除され、実行可能ファイルからロードされました。 しかし、Windowsは、プログラムがアンロードされたブロック内の関数を呼び出そうとしないことをどのように確認しましたか? ハンドルを使用して関数にアクセスし、各関数呼び出しの前に仮想の
LockFunction
呼び出すことが
LockFunction
ます。 ただし、ウィンドウの表示やDDEコマンドの実行など、多くの関数が「メッセージループ」をひねり、この時間にアンロードすることもできます。 実際、現時点ではそれらのコードは必要ありません。 ただし、「関数ハンドル」を使用する場合、関数セグメントは、呼び出し元の関数に制御を戻すまで解放されません。
代わりに、Windowsは、現在実行されていない関数をアンロードできると想定することから始めます。 Windowsメモリマネージャーのコードが現在実行されているため、 すべての関数をアンロードできます。 この関数がアンロードする前に戻らなかった場合、リンクはプログラムコードまたはスタックのいずれかに残ることができます。
そのため、Windowsは実行中のすべてのタスクのスタック(プロセスとスレッドを分離するまでWindowsのいわゆる実行コンテキスト)を調べ、アンロードされたセグメント内の先頭のリターンアドレスを見つけ、 リロードサンクのアドレスで置き換えます。何も起こらなかったかのように、実行可能ファイルから、制御をその中に転送します。
Windowsがスタック上を歩くことができるように、プログラムは正しい形式でそれをサポートする必要があります。FPOなし、スタックフレームは呼び出し関数のフレームへの
BP
ポインターで始まる必要があります。 (スタックは16ビットワードで構成されるため、
BP
値は常に偶数です。)さらに、Windowsはスタック内のセグメント内(「閉じる」)呼び出しとセグメント間(「遠」)呼び出しを区別する必要があります。それらは、アンロードされたセグメントに正確にはつながりません。 したがって、彼らは、スタック内の
BP
の奇数の値は遠方の呼び出し、つまり すべての遠隔機能は、
INC BP; PUSH BP; MOV BP,SP
プロローグで開始する必要があります
INC BP; PUSH BP; MOV BP,SP
INC BP; PUSH BP; MOV BP,SP
INC BP; PUSH BP; MOV BP,SP
、およびエピローグ
POP BP; DEC BP; RETF
終わる
POP BP; DEC BP; RETF
POP BP; DEC BP; RETF
POP BP; DEC BP; RETF
(実際には、プロローグとエピローグはより複雑でしたが、これは今ではそうではありません。)
スタックからリンクを見つけましたが、他のコードセグメントからのリンクはどうですか? もちろん、Windowsはメモリ全体を調べて、アンロードされた関数へのすべての呼び出しを見つけ、それらすべてをリロードサンクに置き換えることはできません。 代わりに、セグメント間呼び出しは 、呼び出された関数がメモリ内にない可能性があるという事実を考慮してコンパイルされ、実際にはモジュール入力テーブルの 「スタブ」を呼び出します 。 このスタブは、
int 3fh
命令と、関数を探す場所を示す3バイトのサービスバイトで構成されます。
int 3fh
は、リターンアドレスでこれらのサービスバイトを見つけます。 目的のセグメントを定義します。 まだロードされていない場合は、メモリにロードします。 そして最後に、入力テーブルのスタブを関数本体への絶対遷移
jmp xxxx:yyyy
上書きします。これにより、同じ関数への次の呼び出しは、中断することなく1つのセグメント間遷移だけで遅くなります。
これで、Windowsが関数をアンロードするときに、モジュール入力テーブル内の関数が挿入された遷移を
int 3fh
戻すだけで十分です。 システムは、アンロードされた関数へのすべての呼び出しを検索する必要はありません-それらはすべてコンパイル時でも見つかりました! モジュールの「
WinMain
テーブル」には、セグメント間呼び出しの存在についてコンパイラが知っているすべての遠隔関数(特に、エクスポートされた関数と
WinMain
含まれます)、およびポインターによってどこかに渡されたすべての遠隔関数が含まれます。プログラムコードの外部からも、どこからでも呼び出されます(これには、
WndProc
、
EnumFontFamProc
、およびその他のコールバック関数が含まれます)。
離れた関数へのポインタの代わりに、スタブへのポインタがどこにでも渡されます。 つまり、
GetWindowLong(GWL_WNDPROC)
および同様の呼び出しから取得したアドレスも、関数の本体ではなくスタブを指しているということです。
GetProcAddress
でも
GetProcAddress
関数のアドレスの代わりに、DLLエントリテーブルのスタブのアドレスを返します。 (Win32では、DLLの「入力テーブル」の類似物は、「エクスポートテーブル」という名前で残りました。)静的モジュール間呼び出し(DLLからインポートされた関数の呼び出し)は、同じ
GetProcAddress
を使用して解決します
GetProcAddress
。 いずれにせよ、関数をアンロードするときに、スタブを修正するだけで十分であり、呼び出し元のコード自体に触れる必要はありません。
再配置可能なコードセグメントに関するこのすべての知恵は、DOSのオーバーレイリンカーから「継承によって」Windowsにもたらされました。 同様に、最初はスキーム全体- まさにこの形式で -がZortech Cコンパイラに登場し、次にMicrosoft Cに登場しました。Windowsの実行可能ファイル形式が作成されたとき、DOSの既存のオーバーレイ形式が基礎として採用されました。
しかし、Windowsはアンロードするセグメントをどのように選択しますか? ランダムに選択するのは危険です。実行されたばかりのコードをすぐにダウンロードする必要があります。 そのため、Windowsはコードセグメントに「アクセスビット」のようなものを使用します。関数へのすべてのセグメント間呼び出しがスタブを通過することを知って、命令
sar byte ptr cs:[xxx], 1
(
int 3fh
または
jmp
置き換える前)
int 3fh
挿入する
int 3fh
ました
sar byte ptr cs:[xxx], 1
、関数が呼び出されるたびにバイトカウンターを1から0にリセットします。 この命令は5バイトで
int 3fh
ます。既存の実行可能ファイル形式を保存し、カウンター命令を散在させて、1つを介して
int 3fh
をロードできます。
すべてのコードセグメントのカウンター値は1に初期化され、250ミリ秒ごとに、Windowsはすべてのモジュールをバイパスし、更新された値を収集し、LRUリスト内のコードセグメントを並べ替えます。 データセグメントへの呼び出しは、何のトリックもなしで追跡できます。そのような呼び出しはすべて、
GlobalLock
または同様の関数への明示的な呼び出しによって
GlobalLock
マークされて
GlobalLock
ます。 そのため、メモリを解放するためにセグメントをアンロードするときが来ると-Windowsは、最も長くアクセスされていないセグメントをアンロードしようとします:カウンタが最長時間0にリセットされていないコードセグメント、または最も長く続いていないデータセグメント修正されました。
GUIdebookで撮影されたWindows広告1.0-2.1