x 86関数呼び出し規則

9986 ワード

以下は「IDA Pro」から抜粋して、いくつかの細部がはっきり説明されていないようですが、さらに考え、実践する必要があります.
スタックフレームの基本概念を理解したら、次にそれらの構造について詳しく説明します.以下の例は、x 86アーキテクチャおよび一般的なx 86コンパイラ(例えば、Microsoft Visual C/C++またはGNUのgcc/g++)に関連する動作に関する.スタックフレームを作成する最も重要なステップは、関数を呼び出してスタックに関数を格納することです.呼び出し関数は、呼び出される関数に必要なパラメータを格納する必要があります.そうしないと、深刻な問題を引き起こす可能性があります.各関数は、パラメータをどのように受信したいかを示す特定の呼び出し規則を選択し、それに従います.
呼び出し規則は、呼び出し元が関数を配置するために必要なパラメータの具体的な位置を指定します.呼び出しの所定の要件は、パラメータを特定のレジスタ、プログラムスタック、またはレジスタおよびスタックに配置することである.同様に重要なのは、パラメータを渡すときに、呼び出された関数が操作を完了した後、スタックからこれらのパラメータを削除する責任を負う人を決定することです.いくつかの呼び出し規則は、呼び出し元がスタックに配置されたパラメータを削除することを規定し、他の呼び出し規則は、呼び出し関数がスタック内のパラメータを削除することを要求する.指定された呼び出し規則に従うことは、スタックポインタの整合性を維持するために特に重要です.
1.C呼び出し規則
x 86アーキテクチャの多くのCコンパイラは、デフォルトの呼び出し規則をC呼び出し規則と呼ぶ.デフォルトの呼び出し規則が書き換えられた場合、C/C++プログラムでよく使用される_cdecl修飾子は、コンパイラにC呼び出し規則を利用させます.これからは、この呼び出し約束をcdecl呼び出し約束と呼びます.cdecl呼び出し規則は、呼び出し元が右から左の順序で関数パラメータをスタックに入れ、呼び出された関数が操作を完了すると、呼び出し元(呼び出し元ではなく)がスタックからパラメータを消去することを規定している.
右から左にパラメータをスタックに挿入した結果、関数が呼び出されると、一番左の(最初の)パラメータは常にスタックの上部に配置されます.これにより、関数にどれだけのパラメータが必要であっても、最初のパラメータを簡単に見つけることができます.したがって、cdecl呼び出し規則はprintfなどのパラメータ数の可変関数に非常に適している.
呼び出し関数にスタックからパラメータを削除するように要求すると、呼び出された関数によって返された命令がすぐにスタックポインタを調整することがよく見られます.関数が可変数のパラメータを受け入れることができる場合、呼び出し側は、関数のように何個のパラメータが伝達されているかを明確に知っているため、簡単に正確な調整を行うことができるため、このような調整に適している.呼び出された関数は,自分がどれだけのパラメータを受けるかを事前に知ることができず,スタックを必要とする調整が困難である.
次の例では、次のプロトタイプを持つ関数を呼び出します.
void demo_cdecl(int w, int x, int y, int z)

デフォルトでは、この関数はcdecl呼び出しを使用して予定されており、右から左の順に4つのパラメータを押し込むとともに、呼び出し元にスタック内のパラメータを消去するように要求します.コンパイラはこの関数の呼び出しに次のコードを生成する可能性があります.
; demo_cdecl(1, 2, 3, 4);//programer calls demo_cdecl

1. push 4                ;push parameter z

   push 3                ;push parameter y

   push 2                ;push parameter x

   push 1                ;push parameter w

   call demo_cdecl       ;call the function

2. add  esp, 16          ;adjust esp to its former value

1からの4つのpush操作は、プログラムスタックポインタ(ESP発生)16バイト(32ビットアーキテクチャ上4*sizeof(int))の変化をdemo_cdeclが返されると、2で取り消されます.もしdemo_cdeclが50回呼び出されると、呼び出されるたびに2箇所のような調整が発生します.次の例もcdecl呼び出し規則に従っているが、demo_が呼び出されるたびにcdecl後、呼び出し元はスタック内のパラメータを削除する必要はありません.
; demo_cdecl(1, 2, 3, 4) //programemr calls demo_cdecl

  mov  [esp+12],     4   ; move parameter z to fourth position on stack

  mov  [esp+8],      3   ; move parameter y to third position on stack

  mov  [esp+4],      2   ; move parameter x to second position on stack

  mov  [esp],        1   ; move parameter w to top of stack

  call demo_cdecl        ; call the function

この例では、関数の「序文」フェーズで、コンパイラはスタックの上部でdemo_cdeclのパラメータには、ストレージスペースが予め割り当てられています.demo_cdeclのパラメータをスタックに配置する場合、プログラムスタックポインタを変更する必要はありません.そのため、demo_を呼び出します.cdeclが終了すると、スタックポインタを調整する必要もありません.GNUコンパイラ(gccとg++)は,このテクニックを用いて関数パラメータをスタックに配置したものである.いずれの方法を使用しても、関数を呼び出すとスタックポインタが一番左のパラメータを指します.
2.標準呼び出し規則
ここの基準は、マイクロソフトが自分の呼び出し約束のためにつけた名前なので、不適切な言葉があるようです.この規則は関数宣言に修飾子を使用します.stdcall、以下に示します.
void _stdcall demo_stdcall(int w, int x, int y);

標準という言葉が混同されないように,本書の残りの部分では,この呼び出し規則をstdcall呼び出し規則と呼ぶ.
cdecl呼び出し規則と同様にstdcall呼び出し規則は、関数パラメータを右から左の順序でスタックに配置します.stdcall呼び出し規則を使用する違いは、関数の実行が終了した場合、呼び出された関数がスタック内の関数パラメータの削除を担当する必要があります.呼び出された関数の場合、このタスクを完了するには、スタックにどれだけのパラメータがあるかを明らかにする必要があります.これは、関数が受け入れるパラメータの数が固定されている場合にのみ可能です.したがってprintfのような可変数のパラメータを受け入れる関数はstdcall呼び出し規則を使用できない.例えばdemo_stdcall関数には3つの整数パラメータが必要であり、スタックには12バイト(32ビットアーキテクチャでは3*sizeof(int))の空間が占有される.x 86コンパイラは、RET命令の特殊な形式を使用しながら、スタックトップから戻りアドレスを抽出し、スタックポインタに12を加えて関数パラメータをクリアすることができる.demo_stdcallは、次のコマンドを使用して呼び出し元に戻る場合があります.
ret 12    ; return and clear 12 bytes from the stack

stdcallを使用する主な利点は、関数呼び出しのたびにスタックからパラメータをコードで消去する必要がないため、体積がやや小さく、速度がやや速いプログラムを生成できることである.従来、マイクロソフトは、共有ライブラリ(DLL)ファイルから出力されるパラメータの数が固定されているすべての関数に対してstdcall規則を使用しています.共有ライブラリコンポーネントの関数プロトタイプまたはバイナリ互換性の代替を生成しようとしている場合は、この点を覚えておいてください.
3.x 86 fastcall約定
fastcall規則はstdcall規則の変形であり、プログラムスタックではなくCPUレジスタに最大2つのパラメータを伝達する.Microsoft Visual C/C++とGNU gcc/g+(3.4以下(もっと高いはず?--shijianyujingshen)バージョン)コンパイラは、関数宣言のfastcall修飾子を識別します.fastcall規則の使用を指定すると、関数に渡される最初の2つのパラメータがそれぞれECXレジスタとEDXレジスタに配置されます.残りの他のパラメータはstdcallの約束と同様に右から左にスタックに挿入されます.同様にstdcall規則と同様に、fastcall関数は、呼び出し元を返すときにスタックからパラメータを削除する責任を負います.次の宣言ではfastcall修飾子が使用されます.
void fastcall demo_fastcall(int w, int x, int y, int z)

呼び出しdemo_fastcallでは、コンパイラが次のコードを生成する場合があります.
; demo_fastcall(1, 2, 3, 4);   //programer calls demo fastcall

push  4                ; move parameter z to second position on stack

push  3                ; move parameter y to tio position on stack

mov   edx, 2           ; move parameter x to edx

mov   ecx, 1           ; move parameter w to ecx

call  demo_fastcall    ; call the function

注意、demo_を呼び出すfastcallが戻った後、demo_のためスタックを調整する必要はありません.fastcallは、呼び出し元に戻るときにスタックからパラメータyおよびzを消去する責任を負います.2つのパラメータがレジスタに渡されるため、呼び出された関数はスタックから8バイトをクリアするだけで、4つのパラメータを持っていても理解することが重要です.
4.C++呼び出し規則
C++クラスの非静的メンバー関数は、関数を呼び出すオブジェクトを指すthisポインタを使用する標準関数とは異なります.関数を呼び出すオブジェクトのアドレスは、呼び出し元によって提供される必要があります.したがって、非静的メンバー関数を呼び出すときにパラメータとして提供されます.C++言語標準では、非静的メンバー関数にthisポインタをどのように渡すかは規定されていません.そのため、異なるコンパイラが異なるテクニックを使用してthisポインタを渡すのも不思議ではありません.
Microsoft Visual C++は、このポインタをECXレジスタに渡し、stdcallと同様にスタック内のパラメータを非静的メンバー関数でクリアするthiscall呼び出し規則を提供します.GNU g++コンパイラは、thisを任意の非静的メンバー関数の最初の暗黙的パラメータと見なし、他のすべての点でcdecl規則を使用するのと同じである.したがって、g++を使用してコンパイルされたコードでは、非静的メンバー関数を呼び出す前に、thisはスタックの上部に配置され、呼び出し元は関数が返されるときにスタック内のパラメータ(少なくとも1つのパラメータ)を削除する責任を負う.コンパイルされたC++コードのその他の特性は8章で議論する.
5.その他の呼び出し規則
既存の呼び出し規則を完全に紹介するには、本を書く必要がある場合があります.呼び出し規則は、通常、言語、コンパイラ、およびCPU固有です.より珍しいコンパイラで生成されたコードに遭遇した場合は、自分で検討する必要があるかもしれません.ただし、最適化コード、カスタムアセンブリ言語コード、およびシステム呼び出しには、以下の状況に特に注意する必要があります.
ライブラリ関数などの出力関数が他のプログラマに使用される場合は、プログラマがこれらの関数を簡単に呼び出すことができるように、主流の呼び出し規則に従う必要があります.また、関数が内部プログラムのみで使用される場合、関数のプログラムのみが理解する呼び出し規則を使用する必要があります.このような場合、最適化コンパイラは、実行速度の速いコードを生成するために、代替の呼び出し規則を使用することを選択します.このような例としては、Microsoft Visual C++での/GLオプションの使用、およびGNU gcc/g++でのregparmキーワードの使用が挙げられる.
プログラマが面倒を恐れずにアセンブリ言語を使用すると、作成した関数にパラメータを渡す方法を完全に制御できます.他のプログラマが使用する関数を作成することを望んでいない限り、アセンブリ言語プログラマは適切な方法でパラメータを渡すことができます.したがって、カスタムアセンブリコードを分析するときは、特に注意してください.ファジイルーチン(obfuscation routine)やshellcodeでは、カスタムアセンブリコードがよく見られます.
システム呼び出しは、オペレーティングシステムサービスを要求するための特殊な関数呼び出しです.通常、システム呼び出しはステータス変換をもたらし、ユーザーモードからカーネルモードに入り、オペレーティングシステムカーネルがユーザーの要求を実行する.システム呼び出しの開始方法は、OSとCPUによって異なります.例えば、Linux 86システム呼び出しはint 0 x 80命令またはsysenter命令を使用して開始され、他のx 86オペレーティングシステムはsysenter命令のみを使用することができる.多くのx 86システム(Linuxは例外)では、システム呼び出しのパラメータはランタイムスタックにあり、システム呼び出しを開始する前にEAXレジスタにシステム呼び出し番号を格納します.Linuxシステム呼び出しは、特定のレジスタに存在するパラメータを受け入れます.使用可能なレジスタがすべてのパラメータを格納できない場合、メモリに存在するパラメータも受け入れます.