ランタイムコード変更のスマートなケースはありますか?


119

ランタイムコードの変更(実行時にプログラム自体のコードを変更するプログラム)の正当な(スマートな)使用について考えられますか?

最新のオペレーティングシステムでは、検出を回避するためにウイルスがこの手法を使用しているため、これを実行するプログラムを嫌うようです。

私が考えることができるすべては、コンパイル時に知ることができない実行時に何かを知ることによっていくつかのコードを削除または追加するような、ある種のランタイム最適化です。


8
最新のアーキテクチャでは、キャッシュと命令パイプラインに悪影響を及ぼします。自己変更コードはキャッシュを変更しないため、バリアが必要になり、コードが遅くなる可能性があります。また、すでに命令パイプラインにあるコードを変更することはできません。したがって、コードを実行する前に、自己変更コードに基づく最適化を実行して、実行時チェックなどよりもパフォーマンスに影響を与える必要があります。
アレクサンドルC.

7
@Alexandre:自己変更コードは、任意の回数実行されても、変更がほとんど(1回、2回など)変化することは一般的であるため、1回限りのコストはわずかです。
Tony Delroy、

7
これにはCまたはC ++のタグが付けられている理由がわかりません。どちらにもメカニズムがないためです。
MSalters 2011

4
@Alexandre:Microsoft Officeはまさにそれを行うことが知られています。結果として(?)すべてのx86プロセッサは、自己変更コードの優れたサポートを備えています。他のプロセッサでは、コストのかかる同期が必要であり、これにより全体の魅力が低下します。
Mackieメッサー

3
@Cawas:通常、自動更新ソフトウェアは新しいアセンブリや実行可能ファイルをダウンロードし、既存のものを上書きします。次に、ソフトウェアを再起動します。これは、firefox、adobeなどが行うことです。通常、自己変更とは、いくつかのパラメーターのために、ランタイムコードがアプリケーションによってメモリに再書き込みされ、必ずしもディスクに永続化されないことを意味します。たとえば、実行を高速化するために、この特定の実行中にこれらのパスが実行されないことをインテリジェントに検出できる場合は、コードパス全体を最適化する可能性があります。
NotMe

回答:


117

コードの変更には多くの有効なケースがあります。実行時にコードを生成すると、次の場合に役立ちます。

時々、コードは実行時にコードに変換されます(これは動的バイナリトランスレーションと呼ばれます):

コード変更を使用して、命令セットの制限を回避できます。

  • コンピュータがサブルーチンから戻る、または間接的にメモリをアドレス指定する命令を持っていなかった時代がありました(私は知っています)。自己変更コードは、サブルーチン、ポインタ、配列実装する唯一の方法でした。

コード変更のその他のケース:

  • 多くのデバッガーは、ブレークポイント実装するために命令を置き換えます
  • 一部の動的リンカーは、実行時にコードを変更します。この記事では、Windows DLLのランタイム再配置に関するいくつかの背景情報を提供します。これは、事実上コード変更の形式です。

10
このリストは、それ自体を変更するコードの例と、リンカーなどの他のコードを変更するコードの例を混ぜ合わせたようです。
AShelly、2011

6
@AShelly:まあ、もしあなたが動的リンカー/ローダーをコードの一部であると考えるなら、それはそれ自身を修正します。それらは同じアドレス空間に住んでいるので、それは正しい見方です。
Mackieメッサー

1
さて、リストはプログラムとシステムソフトウェアを区別しています。これが理にかなっているといいのですが。結局、どの分類も議論の余地があります。それはすべて、プログラム(またはコード)の定義に正確に何を含めるかにかかっています。
Mackieメッサー

35

これはコンピュータグラフィックス、特に最適化のためのソフトウェアレンダラで行われています。実行時に多くのパラメータの状態が検査され、ラスタライザコードの最適化されたバージョンが生成されます(多くの条件を排除する可能性があります)。これにより、三角形などのグラフィックプリミティブをより高速にレンダリングできます。


5
:面白い読み取りは、DDJのマイケル・アブラッシュの3パートPixomaticの記事でdrdobbs.com/architecture-and-design/184405765drdobbs.com/184405807drdobbs.com/184405848。2番目のリンク(パート2)は、ピクセルパイプラインのPixomaticコード溶接機について説明しています。
typo.pl 2011

1
トピックに関する非常に素晴らしい記事。1984年からですが、今でも良い読み物です。RobPikeとBart Locanthi、John Reiserです。Blit上のビットマップグラフィックスのハードウェアソフトウェアのトレードオフ
マッキーメッサー

5
Charles Petzoldは、「Beautiful Code」というタイトルの本でこの種の例を説明しています。amazon.com/ Beautiful
Leading

3
この答えはコードの生成について語っていますが、問題はコードの変更について尋ねています...
Timwi

3
@Timwi-コードを変更しました。ifの大きなチェーンを処理するのではなく、形状を一度解析してレンダラーを書き直したため、毎回確認する必要なく、正しいタイプの形状がセットアップされました。興味深いことに、これは現在openclコードで一般的です-オンザフライでコンパイルされるため、実行時に特定のケースに合わせて書き換えることができます
Martin Beckett

23

ASMの命令セットは、あなたが可能性がいくつかの必要な指示、欠けているので、一つの正当な理由があるの構築自分自身を。例:x86では、レジスタ内の変数への割り込みを作成する方法はありません(たとえば、axの割り込み番号で割り込みを作成します)。オペコードにコード化されたconst番号のみが許可されました。自己変更コードを使用すると、この動作をエミュレートできます。


けっこうだ。このテクニックの使用はありますか?危険そうです。
アレクサンドルC.

4
@Alexandre C .:覚えているなら、多くのランタイムライブラリ(C、Pascalなど)は、割り込み呼び出しを実行するために関数をDOSタイムで実行する必要がありました。そのような関数は割り込み番号をパラメーターとして取得するため、そのような関数を提供する必要がありました(もちろん、その番号が定数である場合、正しいコードを生成できた可能性がありますが、保証されていませんでした)。そして、すべてのライブラリは自己修正コードでそれを実装しました。
flolo

スイッチケースを使用して、コードを変更せずにそれを行うことができます。ダウンサイズとは、出力コードが大きくなることです
phuclv

17

一部のコンパイラは、静的変数の初期化にこれを使用して、後続のアクセスの条件付きのコストを回避していました。つまり、最初に実行されたときに何もしないでコードを上書きすることにより、「このコードを1回だけ実行する」ことを実装しています。


1
特にmutexのロック/ロック解除を回避している場合は非常に便利です。
Tony Delroy、2011

2
本当に?ROMベースのコード、または書き込み保護されたコードセグメントで実行されるコードの場合、これはどのように行われますか?
Ira Baxter

1
@Ira Baxter:再起動可能なコードを生成するすべてのコンパイラは、少なくとも起動時にコードセグメントが書き込み可能であることを認識しています。したがって、「一部のコンパイラがそれを使用した」という文はまだ可能です。
MSalters 2011

17

多くの場合があります:

  • ウイルスは通常、自己変更コードを使用して実行前にコードを「解読」しますが、この手法は、リバースエンジニアリング、クラッキング、および不要なハッカーのフラストレーションにも役立ちます。
  • 場合によっては、実行中(たとえば、構成ファイルを読み込んだ直後)に、プロセスの残りの存続期間中、特定の分岐が常にまたはまったく行われないことがわかっている特定のポイントが存在する可能性があります。いくつかの変数をチェックして分岐する方法を決定します。分岐命令自体はそれに応じて変更できます
    • たとえば、仮想ディスパッチを特定の呼び出しに置き換えることができるように、可能な派生型の1つだけが処理されることが知られている場合があります。
    • 使用可能なハードウェアを検出すると、一致するコードの使用がハードコードされる場合があります
  • 不要なコードは、ノーオペレーション命令またはそれを飛び越えて置き換えることができます。または、コードの次のビットを直接所定の位置にシフトさせることができます(位置に依存しないオペコードを使用する方が簡単です)。
  • 独自のデバッグを容易にするために記述されたコードは、デバッガーが予期するトラップ/シグナル/割り込み命令を戦略的な場所に挿入する可能性があります。
  • ユーザー入力に基づく一部の述語式は、ライブラリによってネイティブコードにコンパイルされる場合があります
  • ランタイムまで表示されないいくつかの単純な操作をインライン化します(動的にロードされたライブラリからなど)...
  • 自己計測/プロファイリングステップを条件付きで追加する
  • クラックは、それらをロードするコードを変更するライブラリとして実装できます(正確に変更する「自己」ではありませんが、同じ手法と権限が必要です)。
  • ...

一部のOSのセキュリティモデルでは、自己変更コードはroot / admin権限なしでは実行できないため、汎用的に使用することはできません。

ウィキペディアから:

厳格なW ^ Xセキュリティが適用されたオペレーティングシステムで実行されているアプリケーションソフトウェアは、書き込みが許可されているページで命令を実行できません。オペレーティングシステム自体のみがメモリに命令を書き込み、後でそれらの命令を実行できます。

そのようなOSでは、Java VMのようなプログラムでさえ、JITコードを実行するためにroot / admin特権が必要です。(詳細については、http://en.wikipedia.org/wiki/W%5EXを参照してください)


2
自己変更コードにroot権限は必要ありません。Java VMも同様です。
Mackieメッサー

一部のOSがそれほど厳格であるとは知りませんでした。しかし、それは確かにいくつかのアプリケーションでは理にかなっています。root権限でのJavaを実行すると、実際にセキュリティを向上させるんしかし、もし私が...不思議を行う
マッキーメッサー

@Mackie:減らす必要があると思いますが、メモリのアクセス許可を設定してから、有効なuidをユーザーアカウントに戻すことができます...?
Tony Delroy、2011

はい、私は、厳格なセキュリティモデルに伴うアクセス許可を付与するためのきめ細かいメカニズムを備えていることを期待しています。
Mackieメッサー

15

合成OSは、基本的には、部分的にAPI呼び出しに対するあなたのプログラムを評価し、結果をOSのコードを置き換えます。主な利点は、多くのエラーチェックが廃止されたことです(プログラムがOSに愚かなことをするように要求しない場合、チェックする必要がないため)。

はい、それはランタイム最適化の例です。


要点がわかりません。システムコールがOSによって禁止されると言うと、コードをチェックインする必要があるというエラーが返される可能性がありますよね?エラーコードを返す代わりに実行可能ファイルを変更することは、一種のオーバーエンジニアリングのように思えます。
アレクサンドルC.

@Alexandre C.:nullポインターチェックをその方法で排除できる場合があります。多くの場合、引数が有効であることは呼び出し側にとって自明です。
MSalters 2011

@アレクサンドル:あなたはリンクで研究を読むことができます。私は彼らがかなり印象的なスピードアップを得たと思います、そしてそれがポイントになるでしょう:-}
Ira Baxter

2
比較的些細な、I / Oバウンドでないシステムコールの場合、節約はかなりのものです。たとえば、Unixのデーモンを作成している場合、stdioを切断したり、さまざまなシグナルハンドラーを設定したりするために行うボイラープレートのシステムコールがたくさんあります。呼び出しのパラメーターが定数であり、結果は常に同じです(たとえば、stdinを閉じるなど)。一般的なケースで実行するコードの多くは不要です。
マークベッシー2011

1
論文を読んだ場合、第8章には、データ収集のための重要なリアルタイムI / Oに関するいくつかの非常に印象的な数字が含まれています。これは1980年代半ばの論文であり、彼が実行していたマシンは10だったことを覚えていますか?Mhz 68000、彼はソフトウェアでCD品質のオーディオデータ(1秒あたり44,000サンプル)を古いソフトウェアでキャプチャすることができました。彼は、Sunワークステーション(従来のUnix)はその速度の約1/5しかヒットしないと主張しました。私は当時の古いアセンブリ言語コーダーで、これはかなり素晴らしいです。
Ira Baxter

9

何年も前に、ある朝、自己修正コードのデバッグに費やしました。1つの命令が次の命令のターゲットアドレスを変更しました。つまり、分岐アドレスを計算していました。それはアセンブリ言語で書かれていて、プログラムを1命令ずつ実行したときに完全に機能しました。しかし、プログラムを実行すると失敗しました。結局、マシンがメモリから2つの命令をフェッチしており、(命令がメモリに配置されているため)変更中の命令がすでにフェッチされていたため、マシンが変更されていない(正しくない)バージョンの命令を実行していた。もちろん、デバッグしているときは、一度に1つの命令しか実行していませんでした。

自己修正コードはテスト/デバッグが非常に厄介で、マシンの動作(ハードウェアであろうと仮想であろうと)に関する想定が隠されていることがよくあります。さらに、システムは、(現在)マルチコアマシンで実行されているさまざまなスレッド/プロセス間でコードページを共有することはできません。これは、仮想メモリなどの多くの利点を無効にします。また、ハードウェアレベルで行われたブランチ最適化を無効にします。

(注-私はJITを自己変更コードのカテゴリに含めていません。JITはコードの1つの表現から別の表現に変換しています。コードを変更するわけではありません)

全体として、それは単に悪い考えです-本当にきちんと、本当にあいまいですが、本当に悪いです。

もちろん-8080バイトと〜512バイトのメモリしかない場合は、そのような方法に頼らなければならない可能性があります。


1
よくわかりませんが、これについて考えるのに適切なカテゴリではないようです。もちろん、あなたは自分が何をしているか、なぜそれをしているのかを本当に知っているべきです。しかし、そのコードを書いたプログラマーはおそらく、プログラムが何をしているかを見たくないでしょう。もちろん、そのようなコードをデバッグする必要がある場合は厄介です。しかし、そのコードはそのようになることを非常に意図していた。
Mackieメッサー、

最新のx86 CPUは、紙に必要なものよりも強力なSMC検出を備えています:自己変更コードを使用したx86での古い命令フェッチの観察。また、ほとんどのx86以外のCPU(ARMなど)では、命令キャッシュがデータキャッシュと一貫性がないため、新しく格納されたバイトを命令として確実に実行する前に、手動でフラッシュ/同期する必要があります。 community.arm.com/processors/b/blog/posts/…どちらにしても、一度変更して何度も実行しない限り、SMCのパフォーマンスは最近のCPUではひどいものです。
Peter Cordes

7

オペレーティングシステムカーネルの観点から、すべてのJust In Time CompilerおよびLinker Runtimeはプログラムテキストの自己変更を実行します。顕著な例は、GoogleのV8 ECMAスクリプトインタープリターです。


5

自己変更コード(実際には「自己生成」コード)のもう1つの理由は、パフォーマンスのためにジャストインタイムコンパイルメカニズムを実装することです。たとえば、代数式を読み取り、一連の入力パラメータでそれを計算するプログラムは、計算を述べる前に式をマシンコードに変換する場合があります。


5

ハードとソフトウェアの間に論理的な違いがないという古い栗を知っています...コードとデータの間に論理的な違いがないとも言えます。

自己変更コードとは何ですか?値を実行ストリームに入れて、データではなくコマンドとして解釈できるようにするコード。もちろん、関数型言語には、実際には違いはないという理論的な見方があります。eは、命令型言語とコンパイラー/インタープリターで、同等のステータスであると推定せずに、これを簡単に行うことができると言っています。

私が言及しているのは、データがプログラムの実行パスを変更できるという実際的な意味です(ある意味でこれは非常に明白です)。私は、プログラムがコマンドからコマンドに移動するのと同じように、解析から状態への移動(および他の変数の変更)で移動するテーブル(データの配列)を作成するコンパイラーコンパイラーのようなものを考えています。 、プロセス内の変数を変更します。

したがって、コンパイラがコードスペースを作成し、完全に別個のデータスペース(ヒープ)を参照する通常の場合でも、データを変更して実行パスを明示的に変更できます。


4
論理的な違いはありません。しかし、あまり多くの自己修正型の集積回路を見たことはありません。
Ira Baxter

@ Mitch、IMOが実行パスを変更しても、コードの(自己)変更とは関係ありません。その上、データと情報を混同しています。LSE b / cでの私のコメントへのコメントは答えられません。2月から、アメリカ人と英国人は英語を所有していないという私のLovで私のpovを表現するために、3年(1,000日)の間、フォームで禁止されています。
Gennady VaninГеннадийВанин

4

進化を使用して最高のアルゴリズムを作成するプログラムを実装しました。自己変更コードを使用してDNA設計図を変更しました。


2

1つの使用例は、ウイルス対策プログラムをテストするための正規のDOS実行可能COMファイルであるEICARテストファイルです

X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*

実行可能ファイルには、エンコード可能な命令の数を大幅に制限する[21h-60h、7Bh-7Dh]の範囲の印刷可能/入力可能なASCII文字のみを含める必要があるため、自己コード変更を使用する必要があります。

詳細はこちら


DOSでの浮動小数点演算ディスパッチにも使用されます

一部のコンパイラはCD xx、x87浮動小数点命令の代わりに0x34-0x3Bの範囲のxxで出力します。CDは命令のオペコードなのでint、x87コプロセッサが使用できない場合は、割り込み34h-3Bhにジャンプしてソフトウェアでその命令をエミュレートします。それ以外の場合、割り込みハンドラーはこれらの2バイトをに置き換える9B Dxため、後の実行はエミュレーションなしでx87によって直接処理されます。

MS-DOSでのx87浮動小数点エミュレーションのプロトコルは何ですか?


1

Linuxカーネルちょうどそれをロード可能なカーネルモジュールを持っています。

Emacsにもこの機能があり、私はいつもそれを使用しています。

動的プラグインアーキテクチャをサポートするものは、基本的に実行時にコードを変更します。


4
ほとんどありません。常に常駐しているわけではない、動的にロード可能なライブラリを持つことは、自己変更コードとはほとんど関係がありません。
Dov

1

継続的に更新されるデータベースに対して統計分析を実行します。私の統計モデルは、使用可能になる新しいデータに対応するためにコードが実行されるたびに作成および再作成されます。


0

これが使用できるシナリオは学習プログラムです。ユーザー入力に応じて、プログラムは新しいアルゴリズムを学習します。

  1. 同様のアルゴリズムの既存のコードベースを検索します
  2. 同様のアルゴリズムがコードベースにない場合、プログラムは新しいアルゴリズムを追加するだけです
  3. 同様のアルゴリズムが存在する場合、プログラムは(おそらくユーザーの助けを借りて)既存のアルゴリズムを変更して、古い目的と新しい目的の両方を提供できるようにします

Javaでそれを行う方法についての質問があります:Javaコードの自己修正の可能性は何ですか?


-1

これの最良のバージョンはLispマクロかもしれません。単なるプリプロセッサであるCマクロとは異なり、Lispを使用すると、プログラミング言語全体にいつでもアクセスできます。これはlispの最も強力な機能であり、他の言語には存在しません。

私は決して専門家ではありませんが、Lispの人の1人に話してもらいます!彼らがLispが周りで最も強力な言語であり、賢い人々が彼らがおそらく正しいというわけではないと言うのには理由があります。


2
それは実際に自己修正コードを作成しますか、それとも単なるより強力なプリプロセッサ(関数を生成するもの)ですか?
ブレンダンロング、

@Brendan:確かに、これ前処理を行う正しい方法です。ここではランタイムコードの変更はありません。
Alexandre C.
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.